ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

GitHub Actions自动部署Docusaurus文档站:从配置到避坑的全流程指南

GitHub Actions自动部署Docusaurus文档站:从配置到避坑的全流程指南 做项目久了你会发现大量时间其实都花在重复劳动上。就拿 HagiCode 这个项目来说文档站用 Docusaurus 搭建代码放在 GitHub 上维护我一直在琢磨怎么让 GitHub Actions 自动部署跑通省掉手动构建、手动上传那套流程。折腾了几天终于把整条流水线调稳了从触发条件、密钥配置到发布上线全都能自动完成。这篇就把我的配置和踩过的坑完整整理出来适合用 GitHub 托管项目、需要定期更新文档站但不想继续手动操作的朋友看完可以直接照着配。1. 部署自动化这件事为什么值得做1.1 HagiCode 项目里原来的发布流程HagiCode 这个项目早期是用 SVN 做版本控制的发布文档站的方式很原始在本地把 Docusaurus 代码跑起来执行构建命令生成静态文件然后用 FTP 或者直接 scp 传到服务器再把 nginx 指向的目录切换过去。这套流程听起来没什么但真正天天用的时候有一堆问题。首先是环境不一致。我本地 Node 版本是 18同事是 16有时候构建出来的页面行为都不一样明明代码没问题线上就是多一个小怪癖。其次是容易漏文件SVN 的目录结构有时候会带出一些隐藏文件传上去之后页面加载坑坑洼洼。最痛苦的是回滚一旦新版本传上去发现问题得去翻本地的旧构建产物找不找得到全凭运气。后来我统计了一下每次发版最快也要 20 分钟慢的时候一下午都在和服务器环境纠缠。这还没算文档内容本身的修改时间。对一个以内容为主的技术文档站来说这个成本太不划算了。所以当项目迁到 GitHub 之后我第一件事就是研究怎么把部署链路自动化目标很明确代码 push 上去剩下的事全部交给系统。1.2 站在做项目角度Actions、Jenkins、手动部署怎么选在决定用 GitHub Actions 之前我把几个方案放在一起对比过。Jenkins 是老牌选手功能强大生态成熟但它有一个绕不开的问题你得先有台服务器专门跑它。这就意味着要多维护一份环境、一套插件、一堆凭据遇到 Jenkins 本身的版本升级还要小心翼翼地处理。对于 HagiCode 这种中小型项目来说属于典型的重型武器开火之前先得花半天把炮台架好。手动部署就不用多说了效率低、易出错唯一优势是“可控”但这个可控在自动化方案面前其实不值一提。GitHub Actions 最大的优势是它和仓库天然长在一起不需要额外服务器不需要单独维护一套环境。你在仓库里放一个 YAML 文件GitHub 收到 push 事件之后就会自动把任务跑起来整个过程在 GitHub 的服务器上执行和本地环境完全隔离。免费额度对个人项目也很友好公共仓库跑 Actions 不花钱私有仓库每个月也有足够的免费分钟数。而且社区生态丰富构建 Node 项目、推送到分支、发通知这些常见需求都有现成的 Action 可以直接引。对比下来GitHub Actions 对于“代码已经在 GitHub 上的项目”是最短路径这也是我最后选它的核心原因。2. 动手前的关键准备仓库规划与密钥配置2.1 仓库和分支怎么安排才不混乱自动部署不是写一个 workflow 文件就完事前置的仓库规划很重要。HagiCode 用的是最常见的单仓库方案源代码放在 main 分支Docusaurus 构建出来的静态文件放在 gh-pages 分支。为什么用 gh-pages 这个名字因为 GitHub Pages 默认支持直接托管该分支下的文件这样就把“构建”和“发布”两个阶段从源头上分开了。source 分支里是完整的 Docusaurus 项目包括 config 文件、docs 目录下的 Markdown 文章、图片资源等gh-pages 分支里只有构建产物也就是一堆 HTML、CSS、JS 文件。这两个分支互不干扰日常开发只动 source发布过程由 Actions 自动把构建结果推到 gh-pages。好处是就算某次发布出问题我可以直接用 git 操作切到上一次的 gh-pages 提交回滚成本非常低。如果你想把文档站部署到自定义域名那还需要在 static 目录里放一个 CNAME 文件内容是你的域名。这个文件会跟着构建产物一起发布到 gh-pagesGitHub Pages 读到它之后会自动处理域名绑定。我第一次部署的时候忘了这个文件结果站点一直用默认的xxx.github.io/repo地址访问改完重新跑了一次构建才生效所以这块要提前确认。2.2 部署密钥三种方式怎么选workflow 构建完静态文件之后得有一种方式把产物推送到 gh-pages 分支这就涉及权限认证。GitHub Actions 里常见的有三种方式GITHUB_TOKEN、personal access tokenPAT、SSH key。GITHUB_TOKEN 是 GitHub 自动生成的临时令牌每次运行 Actions 任务时动态创建任务跑完自动失效安全风险最小。但它默认权限有限在部署这一场景下需要你在 workflow 里显式声明permissions: contents: write否则推送会失败。PAT 是你在账号设置里手动创建的长效令牌可以精确控制仓库访问范围但它跟着账号走如果账号拥有多个仓库它就能访问这些仓库泄露风险比 GITHUB_TOKEN 高一些。SSH key 是我目前最推荐的方式它是给特定仓库单独生成的 deploy key权限范围被锁定在一个仓库内即使拿到 key 也无法操作其他仓库。流程是本机生成 SSH 密钥对把公钥填到仓库的 Deploy keys 设置里把私钥放进 Actions secrets。之后 workflow 推送 gh-pages 分支时用这个私钥认证。这样最小化授权就算 HagiCode 仓库被恶意利用密钥也影响不到其他项目。2.3 把密钥和变量写进 Actions Secrets配置好 SSH key 之后记得把私钥交给 Actions 使用存放位置是仓库的Settings - Secrets and variables - Actions - New repository secret。Name 我通常用DEPLOY_KEY这样 workflow 里引用${{ secrets.DEPLOY_KEY }}语义非常清晰。有个细节要提醒一下粘贴私钥的时候一定要把完整的-----BEGIN OPENSSH PRIVATE KEY-----到-----END OPENSSH PRIVATE KEY-----整个拷贝进去多一个空格少一个换行都会导致认证失败。我第一次就是只复制了中间的密钥主体workflow 里部署阶段一直报错查了半天才发现是格式问题。除了私钥如果 Docusaurus 构建过程需要访问一些第三方服务的密钥比如给站点加搜索功能用的 Algolia 的 API key也可以放在同一个地方的 secrets 里。Actions 的 secrets 和代码是完全隔离的workflow 运行日志里只会显示***不会泄露具体值这一点比直接把 key 写在代码里安全太多。3. 核心环节手写 GitHub Actions 工作流3.1 触发条件push、PR、手动触发怎么组合workflow 文件放在.github/workflows/目录下文件名随意我用的是deploy-docusaurus.yml。触发方式用 YAML 的on关键字配置HagiCode 用的是组合触发push 到 main 分支时自动部署同时在 Actions 页面保留一个手动触发按钮。on: push: branches: [main] workflow_dispatch:为什么还要保留workflow_dispatch因为有些事情不适合每次 push 都触发比如批量改历史文章、临时想测试某个分支的构建结果。push 触发是无感的只要推代码就会跑而workflow_dispatch可以让你在 Actions 页面点击 Run workflow 手动执行调试的时候非常有用。这里要小心一个坑如果你的 workflow 里同时配置了 push 和 pull_request 触发pull request 合并进 main 时有时候会出现两个任务同时跑。虽然不会影响部署结果但会浪费构建资源而且两个任务同时推 gh-pages 分支可能出现短暂冲突。我的做法是生产部署只监听 pushPR 的验证放进另一个 workflow职责分开避免互相干扰。3.2 构建阶段checkout、Node、依赖缓存、构建触发条件搞定之后接下来就是 workflow 的构建阶段。我用的是 ubuntu-latest runner构建 Node 项目基本不需要额外安装什么开箱即用。第一步用 actions/checkout 拉取源代码第二步用 actions/setup-node 指定 Node 版本第三步做依赖缓存最后执行构建命令。完整配置如下jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkoutv4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-nodev4 with: node-version: 20 cache: npm - name: Install dependencies run: npm ci - name: Build Docusaurus site run: npm run buildcheckout 里我加了fetch-depth: 0这会拉取完整的 git 历史。也许你会问构建静态站需要完整历史吗如果是纯文档站其实不需要。但 HagiCode 后期打算用 git 提交记录生成更新日志完整的 git 历史在 workflow 里是可用的素材所以我提前把这项配置加上了。如果你的项目不需要可以去掉这样首次 checkout 会更快。setup-node 的cache: npm参数是 setup-node action 自带的能力它会自动检查 package-lock.json 的 hash命中缓存的话就跳过 npm ci依赖安装时间从一分钟左右降到十秒以内。缓存对构建速度的提升非常明显强烈建议加上。3.3 部署阶段把静态文件推送到 gh-pages 分支构建完成之后Docusaurus 会在build目录下生成静态文件。接下来的任务就是把这些文件发布到 gh-pages 分支。社区里最常用的是 peaceiris/actions-gh-pages它做的事情很简单清空目标分支旧文件、把指定目录的内容复制过去、提交并推送。远比自己写 shell 命令操作安全。- name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pagesv4 with: deploy_key: ${{ secrets.DEPLOY_KEY }} publish_dir: ./build publish_branch: gh-pages commit_message: ${{ github.event.head_commit.message }}publish_dir是构建产物的目录Docusaurus 默认就是./build。publish_branch指定 gh-pagesdeploy_key对应我们之前配置的 secret。下面说一下为什么要用这个 action 而不是 GitHub 官方的 pages action。官方现在推广的是把 GitHub Pages 的 Source 设为 GitHub Actions然后用 actions/deploy-pages 进行发布。这种方案需要在仓库设置页做调整Pages 的构建过程完全托管在后台。它确实很先进但对“只想把 Docusaurus 产物放到 gh-pages 分支”这种简单需求来说反而把流程变复杂了。peaceiris 这个 action 的思路是传统的“发布分支”模式理解成本低、配置直观而且部署过程全程可见出了问题直接看 gh-pages 分支的状态就能定位。HagiCode 目前没有用到官方 Pages 高级特性所以这个方案是最稳的。4. Docusaurus 这边也得配合好4.1 baseUrl最容易翻车的配置说到 Docusaurus 侧最坑的配置baseUrl 排第一没有之一。baseUrl 决定的是站点资源文件的加载路径。如果你的站点部署在https://user.github.io/baseUrl 应该设为/如果是部署在https://user.github.io/repo/这种子路径下baseUrl 必须写成/repo/注意首尾斜杠都不能省。HagiCode 的站点地址是 GitHub Pages 的项目站点也就是https://hagicode.github.io/hagicode-docs/这种形式所以 docusaurus.config.js 里我是这样配的const config { url: https://hagicode.github.io, baseUrl: /hagicode-docs/, organizationName: hagicode, projectName: hagicode-docs, // ... };之前我犯过一个错误baseUrl 只写了/hagicode-docs少了末尾的斜杠。构建本身不报错但页面打开之后CSS 和 JS 文件的路径全部变成/hagicode-doshagicode-docs/...这种畸形拼接整个页面裸奔成纯 HTML。这个问题的排查思路是这样的先浏览器 F12 看 Network 面板那些 404 的资源请求路径基本能直接暴露出 baseUrl 的错误。如果你拿不准自己的部署环境可以本地执行npm run build然后看build/index.html里资源引用的路径再对比一下实际部署 URL 的路径结构。路径对不对一眼就能看出来不要等部署上线了再查。4.2 构建前必查的三个细节第一个细节是trailingSlash配置。Docusaurus 3.x 默认生成的链接路径是/docs/guide不带尾巴斜杠。GitHub Pages 托管静态站点时访问/docs/guide会尝试找guide目录下的 index.html或者做一次 301 重定向到/docs/guide/。如果你的文章里使用了相对路径引用URL 重定向之后资源路径很容易错位。我的建议是直接在配置里把trailingSlash: false写明白让链接行为在本地和线上保持一致省得两边结果不一样。第二个细节是static目录下的文件会不会被正确复制到build目录。Docusaurus 有个机制static目录下的文件会原样复制到构建产物的根目录。比如放一个CNAME文件构建之后会在build/CNAME里出现部署之后才能被 GitHub Pages 识别。如果你发现自己部署了但自定义域名没生效多半是 CNAME 文件没放对位置或者放了但没重新构建。第三个细节是 Node 版本一致性。本地能构建成功不代表 Actions 里也能最常见的原因就是 Node 版本差异。Docusaurus 3.x 对 Node 要求是 18 以上我本地用 20workflow 里 setup-node 也固定成 20两边一致问题基本不会出现。如果你的项目根目录放了.nvmrc文件setup-node 会优先读取它这个方式团队协作时更友好大家统一版本不靠口头约定。5. 从零到上线的真实操作过程5.1 第一次 push我在 Actions 页面看到了什么配置好 workflow 和 Docusaurus 之后我第一次把改动推上 main 分支然后打开仓库的 Actions 页面。页面上立即出现了一个正在运行的实例名称是我在 workflow 的name字段设置的值状态栏在转圈。点进去之后能看到任务执行的三个大阶段Checkout source code、Setup Node.js、Install dependencies、Build、Deploy。checkout 阶段很快几秒钟就完成。setup-node 这里有个细节值得注意它会输出当前 Node 版本和 npm 版本第一眼就要确认这个版本是不是和本地一致不一致的话后面构建很容易踩坑。npm ci 阶段如果命中了缓存日志会显示Cache hit安装时间大幅缩短。build 阶段 Docusaurus 会输出一堆构建日志最后出现Site generated in X seconds才说明构建成功。Deploy 阶段最紧张它会执行一次 git 操作把构建产物推送到 gh-pages 分支。第一次跑的时候我盯着页面看到Deploy to GitHub Pages的步骤由绿色打勾标志结束后我心里才踏实。随后去 gh-pages 分支那边看一眼发现新提交已经推上去了提交信息是我在 workflow 里设置的commit_message。5.2 发布完成后怎么自检部署成功之后不要直接关闭页面我习惯做一轮快速自检。首先打开站点首页确认能正常加载样式没有崩溃。接着随便打开一篇文档详情页看正文格式、代码块高亮、目录侧边栏是否正常。然后我会按 F12 切到 Network 面板刷新页面重点看有没有红色状态的资源请求。常见的是图片 404、字体 404这些有时候页面看起来没大问题但控制台里已经报错了。最稳妥的办法是找一个页面用 curl 手动请求一遍比如curl -I https://hagicode.github.io/hagicode-docs/响应里如果是200 OK说明首页正常。再抽查几篇文章的 URL比如/hagicode-docs/docs/intro返回 200 就说明静态文件都被正确生成和托管的。顺带一提如果某个链接返回 404直接去build目录里找对应路径大概率是 Docusaurus 的链接生成方式和你的预期不一致在本地就能定位。5.3 日常更新文档的完整流程自动化跑通之后日常更新文档的流程已经简化成三步。第一步本地修改 Markdown 文件然后执行npm run build快速验证会不会有构建错误第二步提交代码到 main 分支并 push第三步等大约两分钟GitHub Actions 自动构建并部署站点更新完成。这个流程已经陪伴 HagiCode 跑了一个多月再没有出现过上传漏文件、线上资源 404、环境不一致这类问题。有个体验上的提升值得单独说一下以前用 SVN 手动部署的时候总是担心本地构建产物和服务器上运行的有差异因为中间隔了很多步现在 Actions 每次都在全新的 runner 环境里构建构建环境和线上环境完全一致出问题率几乎为零。这种“确定性”本身是自动化带来的最大价值。6. 常见问题与排查技巧实录6.1 npm ci 报错锁文件不一致第一次配完 workflow我在 Actions 日志里看到npm ci can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync。这个错误的意思是 package-lock.json 和 package.json 不同步通常发生在你改了 package.json 里的依赖但没执行 npm install 更新锁文件。解决办法是本地跑一遍npm install,把最新的 package-lock.json 提交到仓库。顺带说明一下为什么 workflow 里用npm ci而不是npm installnpm ci 严格按照锁文件安装不会偷偷改版本能保证本地和 CI 环境依赖一致这在自动化部署里非常重要。6.2 部署成功但页面样式全挂这是 baseUrl 没配好的典型症状。页面能打开但是 CSS、JS、图片全 404布局完全乱掉。排查步骤很简单打开 Network 面板看资源请求的实际 URL把里面奇怪的前缀和你的url、baseUrl配置对照一下问题基本当场就能暴露。如果本地构建的 html 里资源路径是对的部署到 GitHub Pages 之后才错那就检查一下organizationName和projectName是否符合实际仓库名。还有一个冷门情况如果你用的是组织和用户的 Pages 站点根路径但 workflow 里部署到了项目子路径也会出现这种路径错位。6.3 推送 gh-pages 分支时报 Permission denied如果 Deploy 步骤报Permission to gitgithub.com:xxx/xxx.git denied首先检查 secrets 里是不是真的配置了DEPLOY_KEY其次检查 workflow 里deploy_key引用的名字是不是和 secrets 的名称完全一致。大小写也要注意secrets 的名称对大小写敏感。另一个容易被忽略的点如果你用的是 GITHUB_TOKEN 方式需要在 workflow 顶层显式加上permissions: contents: writeGitHub 在 2023 年之后默认收紧了 token 权限不加这行就会推送失败。SSH key 方式一般不需要额外设置权限但 key 的私钥格式必须标准。6.4 workflow 过程一直卡在排队或超时偶尔会看到 Actions 任务长时间停在 queued 状态这个通常是 GitHub 的 runner 资源波动等几分钟就好。如果经常性排队可以看一眼是不是并发任务太多免费账号的并发数有限。真正需要优化的是构建时间依赖安装、缓存命中、构建耗时这几项按优先级处理。我的经验是先把缓存开起来效果最明显接着看构建日志里哪一步耗时最长有针对性的优化。比如 Docusaurus 项目里如果本地搜索插件会抓取大量文档内容这一步可能比较耗时可以考虑用增量构建或者精简搜索索引规模。6.5 常见问题速查表现象可能原因解决办法npm ci 失败锁文件与 package.json 不同步本地执行 npm install 并提交锁文件页面样式全挂baseUrl 配置错误检查 docusaurus.config.js 的 baseUrlDeploy 阶段 Permission deniedsecrets 名称不匹配或 token 权限不足核对 secrets 名称显式声明 permissions自定义域名不生效static 目录缺少 CNAME 文件在 static 目录新建 CNAME 文件并重新部署构建成功但 Pages 404部署分支选择错误确认仓库 Pages 设置里的分支是 gh-pagesActions 任务超时依赖安装或构建耗时过长开启缓存、优化构建逻辑这套配置跑通之后HagiCode 的文档更新流程基本就不需要我操心了。我个人的体会是自动化部署这件事前期花半天时间把 workflow 调好后面节省的时间是长期的。如果你也正在折腾 Docusaurus 和 GitHub Actions 的集成照着这个流程走遇到问题直接回来翻一下排查表应该能少走不少弯路。最后再提醒一句第一次配置的时候多花几分钟把 baseUrl 和密钥这两件事确认对了后面就顺了。
返回列表