用 GitHub Actions 自动构建并部署本站
这是熵桥知识库的第一篇文档,也是这套站点工作的"动力源"。本文把整个 CI/CD 流程一次性写清楚 —— 从仓库结构、Workflow 文件、密钥配置,到如何"动态发布"一篇新文章。
你想发新文章 → 在
content/kb/写 Markdown →git push→ GitHub Actions 拉起、构建、部署。整个回路 5 分钟内跑完。
这套流水线在做什么
站点是 Next.js 15 的 静态导出(next.config.ts 中 output: 'export')。每次 push 到 master 分支,CI 都会:
- 拉取代码
- 安装依赖(带 pnpm 缓存)
- 跑
pnpm build,把content/kb/*.md在编译期渲染成 HTML - 上传
out/静态产物 - 推送到 GitHub Pages(或你指定的对象存储 / 服务器)
新文章 = 新增一个 .md 文件 = 触发一次构建 = 上线。"动态发布"靠的是这个流水线,而不是后端。
仓库结构
eb-website/
├── .github/workflows/deploy.yml ← 这一篇要写的
├── content/
│ └── kb/ ← 你的 Markdown 都放这里
│ ├── github-ci-deploy.zh.md
│ └── github-ci-deploy.en.md
├── lib/kb.ts ← 编译期读取 markdown
└── app/kb/[slug]/page.tsx ← 用 generateStaticParams 生成每篇页面
文件命名规则:
<slug>.zh.md— 中文版本<slug>.en.md— 英文版本<slug>.md— 单语言(默认归到英文)
只有一个语言版本时,前端会自动 fallback 到存在的那一个。
Markdown 文件的 frontmatter
每篇文章顶部带一段 YAML,编译期被 gray-matter 读出来:
---
title: "文章标题"
excerpt: "列表页展示的一句话摘要"
cat: devops # ai / build / launch / business / devops / respproxy
read: 12 # 阅读分钟数;不写的话会按字数估算
updated: "2026-05-03" # ISO 日期,用于排序与展示
---
支持的分类(cat):
| 值 | 含义 |
|---|---|
ai |
用好 AI |
build |
构建与发布 |
launch |
上线与增长 |
business |
一个人的公司 |
devops |
DevOps 与 CI |
respproxy |
RespProxy 文档 |
Workflow 文件:.github/workflows/deploy.yml
下面这份配置已经写在仓库里了,逐段解释一下:
name: Build & Deploy
on:
push:
branches: [master, main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages-deploy
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
env:
NEXT_PUBLIC_SITE_URL: ${{ vars.SITE_URL }}
- name: Disable Jekyll on Pages
run: touch out/.nojekyll
- uses: actions/upload-pages-artifact@v3
with:
path: out
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
几个关键点:
concurrency保证同一分支只跑一个部署,新提交会取消旧的,避免一个旧版盖掉新版。permissions是 GitHub Pages 部署必须的;缺一个 token 就 403。--frozen-lockfile让 CI 用pnpm-lock.yaml里那一份精确版本,本地装的版本对不上 CI 会直接失败 —— 这是好事。.nojekyll必加,否则 Pages 会用 Jekyll 处理_next/目录里的下划线文件,资源 404。
GitHub 仓库的一次性配置
进 GitHub 仓库 → Settings:
- Pages → Source 选 GitHub Actions(不是 "Deploy from a branch")。
- Actions → General → Workflow permissions → 选 Read and write permissions。
- Environments → New environment → 新建
github-pages。 - Variables 里加一个
SITE_URL,值填你的最终域名,比如https://entropybridge.com。
如果你部署到 自有服务器或对象存储(OSS / COS / Cloudflare R2),把 deploy job 替换成对应的上传 step:
# 阿里云 OSS 示例
- uses: manyuanrong/setup-ossutil@v3.0
with:
endpoint: oss-cn-beijing.aliyuncs.com
access-key-id: ${{ secrets.OSS_KEY }}
access-key-secret: ${{ secrets.OSS_SECRET }}
- run: ossutil cp -rf out oss://your-bucket/ --update
# Cloudflare Pages 示例
- uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CF_ACCOUNT_ID }}
projectName: entropybridge
directory: out
密钥统一放在 Settings → Secrets and variables → Actions → Secrets,不要写进仓库。
发一篇新文章的完整动作
# 1. 拉最新主分支
git switch master
git pull --rebase
# 2. 新建 markdown
cat > content/kb/my-new-post.zh.md <<'EOF'
---
title: "我的新文章"
excerpt: "一句话说清楚这篇在讲什么"
cat: build
read: 6
updated: "2026-05-03"
---
# 我的新文章
正文从这里开始……
EOF
# 3. 提交
git add content/kb/my-new-post.zh.md
git commit -m "kb: my new post"
git push
# 4. 去 GitHub → Actions 看 build 进度
# 成功后访问 https://entropybridge.com/kb/my-new-post/
整个动作不需要碰任何代码 —— 这是这套设计的关键:写作和工程解耦。
本地预览
写完先在本地跑一下,免得 CI 才发现 frontmatter 写错:
pnpm install
pnpm dev
# 浏览器访问 http://localhost:3000/kb
或者跑一次完整构建模拟 CI:
pnpm build
pnpm serve # 起个本地静态服务器看 out/
常见坑
pnpm install 在 CI 报 lockfile 不一致。
本地用了和 CI 不同版本的 pnpm。把 workflow 里的 pnpm/action-setup 版本和你 package.json 的 packageManager 字段对齐。
Pages 上线后样式全丢、JS 404。
没加 out/.nojekyll,或者你的 site 不是部署在 root 而是某个子路径(如 /eb-website)。后者要在 next.config.ts 设 basePath:
const nextConfig: NextConfig = {
output: "export",
basePath: process.env.NODE_ENV === "production" ? "/eb-website" : "",
};
新加的 markdown 没出现。
检查文件名是不是 .zh.md / .en.md 结尾;frontmatter 的三横线 --- 上下都要有;YAML 里中文字符串记得用引号包起来。
generateStaticParams 不识别新文章。
这是构建期 read filesystem 的阶段,所以一定要 git add 进仓库后再 push;本地有但没 commit 的文件 CI 看不到。
进一步
- 想加 PR 预览:在
on:加pull_request,把 deploy step 换成 Cloudflare Pages 或 Vercel preview。 - 想做 RSS / sitemap:编译期复用
lib/kb.ts里的getKBIndex(),写到out/rss.xml与out/sitemap.xml(脚本已有半成品,可直接扩展scripts/generate-static-files.js)。 - 想加全文搜索:用 Pagefind,CI 在 build 之后跑
pagefind --site out即可,全静态、零成本。
写到这里,第一条流水线就齐了。下一篇推荐看《一个人的技术栈》,把这一套放进更大的工程上下文里。