Pages CMS + Hexo 8 实战:像发 QQ 空间一样写博客,并用 GitHub Actions 自动发布
我一直想把写博客这件事变简单一点。
Hexo 本身很好用,但“写一篇随笔”通常意味着打开编辑器、找到 Markdown 文件、补全 frontmatter,再进终端执行命令。电脑在手边还好,换成手机就很麻烦。写着写着,人会开始惦记构建有没有报错、分支有没有推对,原本的一点表达欲也被消耗掉了。
我想要的其实很朴素:像以前发 QQ 空间一样,打开网页,写完,点保存。至于 Hexo、Git 和部署,让它们在后面自己跑。
金猪博客最后接入了 Pages CMS。它不是把 Hexo 改造成动态网站,也没有另外建一套内容数据库。文章仍然是 GitHub 仓库里的 Markdown,Pages CMS 只是给这些文件套了一层在线编辑器。
本文记录本站实际使用的配置,也把刚刚遇到的一次“CMS 明明保存了,怎么没看见 Actions”的排查过程写下来。
本文于 2026-07-30 使用 Hexo 8.1.2、Keep 4.3.0、Node.js 22 和 Pages CMS 托管版验证。Pages CMS 仍在更新,配置项应以文末官方文档为准。
最终的发布链路
先看全貌。本站不是让 Cloudflare 直接构建 Hexo,而是保留两个分支:
source保存 Markdown、主题配置、脚本和依赖;main只保存 Hexo 生成后的静态网站。
一次正常保存会经过下面几步:
- 在 Pages CMS 编辑文章;
- Pages CMS 把 Markdown 提交到
source; source的push触发 GitHub Actions;- Actions 安装依赖,执行清理、构建和检查;
- 检查通过后,把
public/发布到main; - Cloudflare Pages 监听
main,把新版本部署到dajinzhu.cn。
这里有一个好处:CMS 没有直接碰正式网站。即使某篇文章的日期、链接或公式写坏了,也要先经过构建检查。检查不通过,网站继续保留上一个正常版本。
如果还没有搭好 Hexo 和 Cloudflare Pages,可以先看本站的 Hexo 8 + Keep 4 部署记录。本文从源码仓库已经可以正常构建开始。
1. 让 Pages CMS 认识仓库
最省事的方式是使用官方托管版:
- 打开 app.pagescms.org;
- 使用 GitHub 登录;
- 安装 Pages CMS GitHub App;
- 授权需要编辑的仓库;
- 打开仓库的
source分支。
这里一定要确认分支。本站默认分支就是 source,CMS 保存的也是 source。如果误开 main,看到的只会是生成后的 HTML,既不适合编辑,也不会触发源码构建。
Pages CMS 会读取仓库根目录的 .pages.yml。没有这个文件时,它不知道文章放在哪里,也不知道 title、date 和 tags 应该用什么控件编辑。
2. 先配图片目录
本站把文章图片放在:
1 | source/images/posts/ |
对应的 Pages CMS 配置是:
1 | media: |
input 是文件在 GitHub 仓库中的位置,output 是写进 Markdown 的公开地址。两者不是一回事。
例如,在 CMS 中上传:
1 | source/images/posts/pages-cms-hexo/publishing-flow.svg |
文章里应该引用:
1 |  |
rename: safe 会把不适合作为 URL 的文件名处理得规整一些。我仍然习惯在上传前使用简短的英文文件名。以后迁移服务器或批量处理图片时,会少很多麻烦。
3. 把 Hexo 文章声明成一个集合
Hexo 的文章集中在 source/_posts,每篇文章都有相同的 frontmatter 结构,因此适合使用 Pages CMS 的 collection:
1 | content: |
format: yaml-frontmatter 很关键。它告诉 CMS:两个 --- 之间是结构化字段,下面才是 Markdown 正文。
为了让文章列表更好找,我还配置了排序和搜索:
1 | view: |
文章多起来后,这一段比想象中有用。否则几十个 Markdown 文件堆在一起,在线编辑器和文件管理器没有本质区别。
4. 编辑器字段如何对应 Markdown
Pages CMS 的字段不是另一份数据。保存之后,它们仍然回到同一个 .md 文件。
本站目前使用这些字段:
1 | fields: |
body 是一个特殊字段。对于 frontmatter 格式,它不会生成 body:,而是写到第二个 --- 后面。富文本编辑器可以直接排版,也可以切换到 Markdown 源码。遇到表格、代码块和复杂链接时,我更喜欢切到源码检查一遍。
分类在本站是一个字符串,标签则是列表。这样能避免同一篇文章挂三四个宽泛分类,同时保留更细的检索入口。
description 单独设置长度检查,是因为首页摘要不一定适合搜索结果。写它时我尽量回答两个问题:这篇文章解决什么问题,读者能得到什么。它不是关键词袋子。
5. 保存时保留未暴露的字段
Pages CMS 只认识 .pages.yml 中声明的字段。如果文章还有 reviewed、cover 等暂时不准备放进编辑器的 frontmatter,保存时不应该把它们删掉。
本站开启了合并保存:
1 | settings: |
merge: true 会把编辑器提交的字段合并回原文件,未被编辑器管理的键得以保留。提交信息也做了统一,因此在 GitHub 历史里,一眼就能分出内容修改和程序修改。
6. GitHub Actions 才是真正的发布者
CMS 保存完成,只代表 source 多了一个 Commit。网站能不能更新,要看后面的工作流。
本站的触发条件是:
1 | name: Build and deploy Hexo |
正常保存使用 push。workflow_dispatch 是手动重新部署入口,后面会讲。
构建步骤没有直接调用一条裸 hexo generate,而是把安装、构建和检查分开:
1 | steps: |
本站的 check 会检查 description、canonical、JSON-LD、站点地图、图片和本地链接,也会检查 Tags、Categories 是否为空。前几天 Keep 主题曾偶发把一段 Stylus 源码当成页面局部模板,这类异常现在也会在部署前被拦住。
构建通过后,工作流把 public/ 同步到 main:
1 | rsync -a --delete --exclude ".git/" public/ "$RUNNER_TEMP/deploy/" |
--delete 的意义是让 main 精确等于本次构建结果。文章改名或删除后,旧 HTML 不会永久残留。
7. 给 CMS 增加“重新部署”按钮
这里最容易误解。
Pages CMS 保存文章后,本来就会产生 GitHub push,不需要再点一次 Actions。Pages CMS 的 Actions 功能,是额外添加一个手动按钮,用在“内容没有变化,但我想重新构建”或者“刚修复了外部服务,想重跑一次”的场景。
本站在 .pages.yml 根级加入:
1 | actions: |
workflow 写 .github/workflows/ 下的文件名;ref: current 表示使用 CMS 当前打开的分支。相应工作流必须声明 workflow_dispatch,并接受 Pages CMS 传来的 payload。
这个按钮是备用入口,不应该成为每次发文的固定步骤。正常流程仍然是保存一次,等待自动构建。
8. 一次真实的“它到底执行没有”
配置完成后,我在 CMS 把一篇随笔的分类改了一次。页面没有给出我预想中的强烈反馈,于是第一反应是:“是不是没有交给 Actions?”
后来没有继续猜,而是把 GitHub 上的时间对齐:
1 | 19:26:32 CMS 提交到 source |
CMS 提交和 Actions 触发只差 3 秒。它确实执行了,只是 CMS 的“保存反馈”和 GitHub 的“工作流状态”分属两个界面。
再后来我又把分类从 Think 改成“随笔”,GitHub 出现了第二个内容提交。本地仓库当时还是旧版本,因此继续在本地写文章前,必须先:
1 | git pull --ff-only origin source |
这是在线 CMS 与本地编辑共存时最需要养成的习惯:开始本地工作前先同步,避免把手机上刚保存的内容覆盖掉。
9. 保存后网站没更新,按顺序查
第一站:source 有没有新提交
没有新 Commit,问题还在 CMS 这一侧:
- 是否点了保存;
- 是否选中了正确仓库;
- 当前是不是
source; - GitHub App 是否仍有仓库权限;
- 字段校验是否没有通过。
此时 Actions 没有运行是正常的,因为 GitHub 根本没有收到新的 push。
第二站:Actions 有没有同一个提交号
找到 CMS 提交的短 SHA,例如:
1 | c2da94e |
再到 Actions 中找 head_sha 相同的运行。不要只按“几分钟前”判断。连续保存两次时,两个运行可能非常接近,前一个还可能因为并发策略被取消。
如果完全没有运行,检查:
1 | on: |
分支名、缩进和工作流所在分支都要正确。
第三站:main 有没有新产物
Actions 变绿之后,再看 main。以本站为例,文章应该生成到:
1 | 2026/07/30/pages-cms-hexo-github-actions/index.html |
分类页、标签页、首页和站点地图也应一起变化。不能只看到工作流成功,就默认每个页面内容都正确。
第四站:Cloudflare 是否部署了这个 main 提交
Cloudflare Pages 监听的是 main,不是 source。打开 Pages 的部署记录,比较它显示的提交号与 GitHub main 最新提交。
如果提交号落后,问题在 Cloudflare Git 集成;如果已经一致,才进入缓存排查。
第五站:最后再看缓存
前四站都正确,但正式域名仍是旧内容,可以依次尝试:
- 浏览器强制刷新;
- 无痕窗口;
- 比较
pages.dev与正式域名; - 查看响应头中的缓存状态;
- 必要时清理对应 URL 的 Cloudflare 缓存。
一上来就全站清缓存,往往会掩盖真正的问题,也不能修复没有生成的 HTML。
10. 我现在怎么发一篇文章
日常发布已经很短:
- 用手机或电脑打开 Pages CMS;
- 新建文章,先关闭“公开发布”;
- 填标题、日期、description、分类和标签;
- 写正文并上传图片;
- 保存草稿;
- 预览和校对;
- 打开“公开发布”,再次保存;
- 到 Actions 看本次提交是否通过;
- 上线后检查文章页和分类页。
草稿也会提交到 source,但 Hexo 不会把 published: false 的文章放进正式网站。这样既保留了 Git 历史,也不用在 CMS 之外再维护一份草稿。
11. 几个容易留下后患的细节
不要直接编辑 main
main 是构建产物。下次 Actions 发布时,它会被 public/ 覆盖。所有正文修改都应该发生在 source/_posts。
手机适合写,复杂排版仍要预览
普通段落、标题、列表和图片在 CMS 中很顺手;复杂表格、数学公式和嵌套代码块,我还是会切换 Markdown 源码。所见即所得不等于生成结果永远一致。
分类和标签要提前约定
技术、Tech、tech 可能生成不同入口。本站约定分类尽量使用少量稳定名称,标签再承担细分主题。否则半年后分类页会像没有整理过的下载文件夹。
不要用修改日期假装内容更新
只有正文确实补充、修正或重新验证时才更新 updated。单纯为了让搜索结果显得新,既没有帮助,也会让读者误判文章的有效期。
CMS 不是备份,Git 才是
Pages CMS 只是编辑入口。真正可回退的历史仍在 GitHub Commit 中。改坏 frontmatter 时,可以比较前后版本,而不是靠回忆恢复。
结语
接入 Pages CMS 之后,Hexo 没有变成另一个系统。文章还是 Markdown,构建还是 Hexo,历史还是 Git,Cloudflare 也仍然只负责部署静态文件。
变化只发生在写作入口:过去必须坐到电脑前才能完成的操作,现在拿出手机也能做。保存之后到底发生了什么,又没有被藏进一个看不见的黑盒;从 source 到 Actions、main 和 Cloudflare,每一步都有提交号可以核对。
对我来说,这种状态比“完全无感的一键发布”更踏实:平时足够简单,出问题时又查得到。