博客构建链路改造记录
博客构建链路改造记录
2026年8月,我把这个博客的构建与部署链路做了一次大改造:从”本地构建 + 提交产物”变成”仓库只存原料 + GitHub Actions 自动构建发布”。这篇文章记录改造动机、关键变更、以及过程中踩到的坑,方便日后回溯。
背景:改造前的链路
改造前的流程是:
- 在
blog_builder/(一个独立于仓库的 Hexo 工程目录)里跑hexo generate - 用脚本把产物 rsync 到仓库的
docs/ - 手动提交
docs/并 push
这套流程的问题:
docs/里全是构建产物(HTML/CSS/JS),每次重建都产生大量无意义的 diff 和历史- 图片(约 17MB)作为普通文件躺在 git 里,仓库体积被撑大
- Hexo 工程本身没有进仓库,换机器需要重新手工搭建环境
目标架构
1 | 仓库(只存原料) |
关键点:Pages 直接以 LFS 指针文件形式暴露会挂(Pages
官方不支持 Git LFS,分支里的 LFS
文件只会以指针文本形式服务出去),所以图片必须经 CI 的
checkout(lfs: true) 拉成真实文件、构建进产物、再以 artifact
形式发布。
主要变更(对应 git 记录)
1. 重构:builder
工程入仓,docs 退役(commit 73e5752)
- 把
blog_builder/的完整 Hexo 工程(_config.yml、_config.nexmoe.yml、package.json、scaffolds/、bin/manager、check_tags.js)合入仓库builder/ - 图片从
docs/img/迁移到builder/source/img/(真实文件),favicon 同理 docs/整体删除并加入.gitignore——从此它是纯构建产物,不再进版本库- 新增
.gitattributes:栅格图片(png/jpg/jpeg/gif/webp/bmp/tiff/ico)走 Git LFS
2. 历史重写(filter-repo + lfs migrate)
git filter-repo --path docs --invert-paths:把全部历史中的docs/产物抹掉(md 文章历史原样保留)git lfs migrate import:把图片 blob 转成 LFS 对象- 效果:git pack 从 ~25MB 缩到 ~7MB,图片作为 59 个 LFS 对象单独存储
3. 修复:CI 里的 mtime
脚本(commit b9dae31)
见下方”踩坑记录”第 2 条。
4.
安全:依赖升级消除全部漏洞(commit 5e53e3b)
hexo-filter-mathjax0.9.1 → 0.11.1hexo-generator-feed3.0.0 → 4.0.0- 用
overrides消除嵌套的 hexo 6.3.0(<7.2.0有高危漏洞) npm audit归零
5. 修复:mathjax ams
扩展缺失(commit 9d5a956)
见下方”踩坑记录”第 4 条。
踩坑记录
1. 仓库里现成的软链是坏的
改造前仓库的 builder/source/ 里就有
_posts -> ../raw/ 软链,但解析结果是
builder/raw/(不存在)——正确写法是
../../raw/。builder/source/img、favicon.png
同理。教训:相对软链的层级要在迁移时实测 realpath
验证。
2. CI 里
git ls-files 的引号陷阱
为了在 CI 里恢复文件的 mtime(_config.yml 的
updated_option: mtime 依赖它),我写了:
1 | git ls-files 'raw/*.md' | while read -r f; do touch -d "$(git log -1 --format=%cI -- "$f")" "$f"; done |
结果第一个 CI 构建直接失败。原因:git ls-files
对含空格/中文的文件名会输出带引号 +
八进制转义的形式(如
"raw/Ubuntu\347\232\204advantage tools\345\244\261\350\264\245.md"),read -r
拿到的是字面量引号串,touch 找不到文件,循环退出码非零 →
整个 job 失败。
修复:改用 NUL 分隔:
1 | git ls-files -z 'raw/*.md' | while IFS= read -r -d '' f; do touch -d "$(git log -1 --format=%cI -- "$f")" "$f"; done |
教训:git 输出文件列表给 shell 处理时,一律用 -z
+ read -d ''。
3. 主题的”过时依赖检查”会硬性 kill 构建
升级依赖时,npm audit 报告嵌套的
hexo@6.3.0(<7.2.0 有高危漏洞)——它来自
hexo-theme-nexmoe@4.2.2 声明
hexo: ^6.0.0。我用 npm overrides 把嵌套 hexo
强制统一成 8.1.2,结果构建立刻失败:
1 | Package hexo's version (8.1.2) does not satisfy the required version (^6.0.0). |
原因是主题自带 include/dependency.js,会对 hexo 版本做
semver.satisfies 校验,不满足就
process.exit(-1)。这不是真正的不兼容——主题代码本身在 hexo 8
下运行完全正常,只是声明过时 + 检查粗暴。
解决方案:用 patch-package 固化一个补丁,把主题
package.json 里的 hexo: ^6.0.0 改成
^8.1.1,配合 postinstall: patch-package 在
npm ci 后自动应用。这样既消除了漏洞,又绕过了过时检查。
教训:npm overrides
能改依赖树,但改不掉包自身的运行时断言;npm 生态遇到这种可以 patch
源码文件。
4. mathjax 0.11 的 ams 扩展缺失
升级 hexo-filter-mathjax 到 0.11.1 后,含
align/bmatrix/pmatrix
的页面出现大量 Unknown environment 'align'。
原因:0.11 版的默认包加载行为变了——defaultPackages()
只加载 ['require', 'autoload'],而 ams
扩展(align/aligned/bmatrix/pmatrix
等环境全在
@mathjax/src/mjs/input/tex/ams/AmsMappings.js)只在
tags: 'ams' 时加载。当前配置是
tags: none,所以 ams 没被加载;autoload
的动态加载在此环境下又因 Package.resolvePath
解析失败而不起作用。
修复:在 _config.yml 的 mathjax 段显式加载 ams:
1 | mathjax: |
验证:三个受影响页面的 Unknown environment 从 86+
处归零,tags: none 保持则没有公式编号副作用。
5. filter-repo 会移除 origin remote
git filter-repo 出于安全考虑会自动删除 origin
remote,重写历史后需要重新
git remote add origin ...。教训:filter-repo
后别急着 push,先确认 remote 还在。
新工作流
现在的日常操作极简:
1 | # 写文章 |
push 后 GitHub Actions 自动完成:拉 LFS 图片 →
npm ci(含 patch 应用)→ hexo generate →
发布到 Pages。docs/ 不再需要人管。
数学公式与 mathjax 的现状
关于公式渲染,早前在2023年4月折腾Hexo和Node里记录过
mathjax + pandoc 的组合(hexo-renderer-pandoc +
hexo-filter-mathjax,pandoc 传 --mathjax
参数),这套组合目前仍然离不开:
pandoc负责 markdown → HTML 的转换(含 LaTeX 语法透传)hexo-filter-mathjax在生成期用 MathJax 做服务端 SVG 渲染(全后端,无需前端 JS)
本次把 hexo-filter-mathjax 升到 0.11.1
后,公式渲染更完整(比如一步状态转移概率矩阵里
bmatrix 环境恢复渲染),但副作用是 @mathjax/src
及其字体包让本地 node_modules 膨胀了约
99MB。目前选择接受(本地磁盘不敏感),后续若在意体积再研究字体裁剪。
结语
这次改造把”构建”从本地手工劳动变成了 CI 的自动化环节,仓库历史从此只记录有意义的内容变更。过程中踩的坑(软链、引号、主题断言、ams 扩展)都写在了上面,希望下次折腾时能少走弯路。
Knighthana
2026/08/08