先说结论:一个内容量不大的静态知识站,可以在不增加 VPS、数据库和自建 CMS 的情况下获得可视化编辑、草稿预览、自动验证和可回滚发布。

这套链路由站主与 Codex 协作完成:站主定义网站目标、成本边界和发布规则,Codex 协助配置、测试和证据整理。本文只把能被仓库和构建结果支持的行为写成事实。

本站的日常编辑约定是:Pages CMS 选择 GitHub 的 main 保存,隔离测试也可以在非生产分支保存;Cloudflare Pages 为非生产分支生成 Preview,GitHub Actions 在发布前重新验证。当前受控发布工作流只在验证通过后,通过普通 push 将 production fast-forward 到指定的 main 提交,最后由 Cloudflare Pages 更新正式站。

这套组合不要求为 CMS 另建 VPS 或内容数据库,但不等于“零成本”。GitHub Actions 受账户套餐额度约束,Cloudflare Pages 也有计划与用量限制,域名还有原有续费。本文讨论的是减少一套自建服务,不是宣称已核对账单或永久免费。

需要的是编辑入口,不是第二套内容系统

这个网站最初已经有一条可靠的最短路径:Astro 读取仓库里的 Markdown,构建静态页面,再由 Cloudflare Pages 托管。真正的摩擦不是缺少数据库,而是每次编辑都要直接处理 frontmatter、文件名和 Git。

WordPress、自托管 CMS 或数据库型 CMS 都能解决更多问题,但也会增加另一组长期责任:运行时升级、备份、权限、数据库迁移、服务监控,以及“仓库和 CMS 到底谁是真实来源”的同步问题。对当前规模来说,这些能力还没有对应的真实需求。

Pages CMS 的定位刚好更窄。它在 GitHub 仓库之上提供编辑界面,保存结果仍是 Markdown 和媒体文件,没有再造一份内容数据库。这延续了本站在先做出知识网站,再设计完整知识系统中的原则:先让真实使用暴露下一层摩擦,再决定要不要扩展架构。

五个环节各自只做一件事

这条链路可以压缩成一行:

Pages CMS、GitHub Actions 与 Cloudflare Pages 的五段式发布链路

Pages CMS 编辑
  → GitHub main 保存
  → Cloudflare Preview
  → GitHub Actions 验证
  → production fast-forward 并由 Cloudflare 正式部署

它看起来只是几个现成工具的连接,关键却在职责边界。

1. Pages CMS:编辑文件

仓库根目录的 .pages.yml 描述了中文和英文内容字段、允许的图片格式、双语配对键和草稿默认值。编辑器可以创建内容,但不能重命名或删除现有条目。settings.content.merge: true 用来保留 Schema 没有暴露的 frontmatter 字段。

Pages CMS 只负责把一次编辑变成 Git 提交。它不决定这次保存能否进入正式站。

2. main:保存日常内容

本站的日常内容约定保存到 main;这不是 Pages CMS 对所有保存的技术强制。这个约定让内容拥有 Git diff、提交历史和 Cloudflare Preview,但不会直接更新正式域名。

把保存和发布拆开很重要。编辑者可以先检查标题、双语配对、移动端换行和链接,不必在按下“保存”时同时承担生产发布的后果。

3. Preview:展示候选,并设置反索引指令

非生产分支允许渲染 draft: true 的内容,并在页面上显示草稿标识。所有 Preview 页面带有 noindex, nofollow,Preview 的 robots.txt 使用 Disallow: /,canonical 仍指向正式域名。这些配置的目标是减少草稿的搜索暴露,不是提供访问控制。

Preview 是否公开取决于 Cloudflare Access 配置;未启用 Access 时,知道 URL 的人可能直接访问。无论是否启用 Access,noindex 都只是索引指令,不是访问控制。Google 只有在抓取页面后才能识别页面内的 noindex;如果 robots.txt 阻止抓取,爬虫反而可能看不到它。

4. GitHub Actions:先验证,再移动生产分支

Pages CMS 里的“验证并发布”按钮触发 workflow_dispatch。工作流检查 payload 中声明的 source、action、repository 和 ref 字段,再确认请求的 SHA 是否等于当前 main,并检查 production 是否仍是 main 的祖先。这是输入合同校验,不是对触发者身份的独立密码学认证。

条件成立后,它才运行:

npm ci
npm audit --audit-level=moderate
npm run verify

npm run verify 又会检查内容 Schema、双语配对、Astro 类型、单元测试、生产构建、草稿排除、canonical、hreflang、sitemap、RSS、内部链接和许可证。任何一步失败,production 都不会移动。

5. production:由当前工作流执行普通 fast-forward

验证通过后,当前受控发布工作流执行普通 push,把 production fast-forward 到精确的 main 提交。它不使用 force push;如果两个分支已经分叉,这条工作流会停止,维护者必须先理解分叉原因。仓库目前没有经过本轮验证的 ruleset 或 branch protection 证据,因此这里不把工作流约束描述成分支本身能阻止所有其他写入。

Cloudflare Pages 只把 production 当作正式发布分支。这样,一次 Pages CMS 保存最多产生 Preview,不会越过验证直接改动 lab.omnihex.xyz。

草稿隔离不是一个开关

这套系统没有把安全寄托在单个 draft 标签上。

生产数据查询只接受 draft === false;静态路由生成会排除草稿;首页、知识列表和 RSS 使用同一组已发布内容;sitemap 过滤草稿 URL;详情页只为非草稿输出 BlogPosting JSON-LD。构建后,scripts/verify-dist.mjs 还会扫描所有 HTML、XML 和文本输出,确认没有草稿 slug。

换句话说,draft: true 同时受到“生成路径”和“构建产物”两层检查。它可以在开发和 Preview 中被人工查看,但不能只靠编辑器状态决定是否公开。

少一套自建服务,不等于零成本

这里只比较这套实现会不会再引入一套必须自己运行的服务,不核算账户的实际账单。

环节 当前项目的固定增量 仍需关注的成本
Pages CMS 托管编辑器 不需自建 CMS 运行时或内容数据库 第三方托管可用性、GitHub App 权限和产品变化
GitHub 私有仓库与 Actions 复用已有 Git 内容源 私有仓库 Actions 超过包含额度可能计费或停止
Cloudflare Pages 静态托管 不需另外运行内容服务器 计划与用量仍有构建、项目、文件数量等限制
域名 使用已经持有的 omnihex.xyz 子域 域名本身仍有原有续费成本

人的维护时间也不是零。有人仍要审阅事实、检查 diff、处理失败构建和决定何时发布。这个系统减少的是额外服务器与内容同步,不是取消编辑责任。

这条链路怎样被验证

验证不是一张架构图,而是几个可以复查的检查点:

  • 接入当时的四篇旧内容显式使用 Boolean draft: false,新内容默认 draft: true;
  • 中英文临时草稿曾在隔离分支完成真实创建、配对、图片上传和清理;
  • 候选分支的 GitHub Actions 同时运行生产构建与 Preview 隔离检查;
  • 当前受控发布工作流只在门禁通过后,把指定的 main 提交 fast-forward 到 production;
  • 后续候选又补上 SEO gate 和正式域名验收;
  • 正式测试检查 sitemap 中的页面、404、验证标记和 pages.dev 重定向。

这些检查点共同证明的是“链路按当前规则运行”,不是所有未来编辑都不会出错。Cloudflare rollback 可以先把当前生产流量切回上一份成功部署,但不会自动修改 Git 分支;紧急回滚后,仍要通过 git revert 或修复提交恢复 Git 历史与实际部署的一致性。

适合谁,不适合谁

这套组合适合内容以文件为主、发布频率有限、希望保留 Git 审计和静态部署优势的小团队或个人站点。它尤其适合已经有 Astro、Hugo 或 Jekyll 项目,只缺一个编辑界面的情况。

如果需要复杂角色权限、多人实时协作、站内表单、评论、支付、个性化查询或大量结构化关系,文件型 CMS 可能不够。那时数据库和应用后端不是“架构过度”,而是业务需求的一部分。

本文的判断不是“所有网站都应该这样搭”,而是:在需求仍然简单时,不要为了获得编辑界面就提前接管一整套服务器。先保留一条能看见 diff、能阻断失败、也能回滚的最短路径,再根据真实摩擦升级。

这也符合公开实验室的内容原则:从真实项目材料整理成可复用知识,同时把失败、边界和验证方法一起留下。

参考资料