返回知识库
DevOps 与 CI12 分钟阅读 · 最近更新 2026-05-03

用 GitHub Actions 自动构建并部署本站(含动态发布工作流)

用 GitHub Actions 自动构建并部署本站

这是熵桥知识库的第一篇文档,也是这套站点工作的"动力源"。本文把整个 CI/CD 流程一次性写清楚 —— 从仓库结构、Workflow 文件、密钥配置,到如何"动态发布"一篇新文章。

你想发新文章 → 在 content/kb/ 写 Markdown → git push → GitHub Actions 拉起、构建、部署。整个回路 5 分钟内跑完。

这套流水线在做什么

站点是 Next.js 15 的 静态导出next.config.tsoutput: 'export')。每次 push 到 master 分支,CI 都会:

  1. 拉取代码
  2. 安装依赖(带 pnpm 缓存)
  3. pnpm build,把 content/kb/*.md 在编译期渲染成 HTML
  4. 上传 out/ 静态产物
  5. 推送到 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

  1. Pages → Source 选 GitHub Actions(不是 "Deploy from a branch")。
  2. Actions → General → Workflow permissions → 选 Read and write permissions
  3. Environments → New environment → 新建 github-pages
  4. 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.jsonpackageManager 字段对齐。

Pages 上线后样式全丢、JS 404。 没加 out/.nojekyll,或者你的 site 不是部署在 root 而是某个子路径(如 /eb-website)。后者要在 next.config.tsbasePath

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.xmlout/sitemap.xml(脚本已有半成品,可直接扩展 scripts/generate-static-files.js)。
  • 想加全文搜索:用 Pagefind,CI 在 build 之后跑 pagefind --site out 即可,全静态、零成本。

写到这里,第一条流水线就齐了。下一篇推荐看《一个人的技术栈》,把这一套放进更大的工程上下文里。