将 Hexo 升级到 8.1.2,并保留 NexT 原有外观

上一次记录 Hexo 升级已经是很多年前了。这次把博客从 Hexo 7.3.0 升级到了 8.1.2,同时把 Node.js 升级到 24 LTS。NexT 仍然保持 8.21.1,不改主题视觉版本,尽量让升级前后的页面看起来完全一样。

这次最大的变化不是版本号,而是终于不再直接修改 themes/next:主题改为由 npm 管理,配置、头像、图标和页脚运行时间都迁到了站点目录。以后更新依赖时,不用再担心自己的修改被覆盖。

最终版本

  • Node.js 24.18.0 LTS
  • npm 11.7.0
  • Hexo 8.1.2
  • NexT 8.21.1
  • Gulp 5

Hexo 8 要求 Node.js 20.19.0 或更高版本,具体要求可以查看 Hexo 官方文档。Node.js 各版本的维护状态可以查看 Node.js Releases

先准备完整回退

博客目录里不只有文章和配置,还有 node_modules、生成后的 public、部署仓库 .deploy_git,以及旧主题目录里的 Git 历史。所以升级前先做了一份完整压缩包,并计算 SHA-256 校验值确认备份可读。

然后在博客根目录建立 Git 基线:

1
2
3
4
5
git init
git add -A
git commit -m "baseline: pre-Hexo-8"
git tag pre-hexo8
git switch -c upgrade/hexo-8

这样有两层回退:普通配置和源码问题可以直接用 Git 定位;如果依赖、生成结果或部署状态也需要恢复,就使用完整压缩包。

固定依赖和 Node.js 版本

以前升级 Hexo 时用过 npm update,这次没有继续这样做。为了让以后重新安装还能得到同一套环境,在 package.json 中固定主要版本:

1
2
3
4
5
6
7
8
9
{
"engines": {
"node": ">=24 <25"
},
"dependencies": {
"hexo": "8.1.2",
"hexo-theme-next": "8.21.1"
}
}

同时清理了误列为直接依赖的传递包,只保留真正使用的 Hexo 插件和构建工具。删除旧的 node_modules 后重新安装并提交 package-lock.json,之后可以直接使用:

1
2
3
npm ci
npm run clean
npm run build

所有命令都调用项目本地的 Hexo,不再依赖全局安装的 hexo-cli

NexT 改为 npm 单一来源

旧博客同时保留过多个 themes/next 目录,而且配置和模板里都有手工修改。这样的结构每次升级都要逐个文件比较,很容易漏掉。

这次把 NexT 的设置合并到根目录 _config.next.yml,再删除旧主题目录,让 theme: next 只解析 npm 安装的 NexT。这个方式也是 NexT 官方推荐的安装方式

头像和 favicon 移到:

1
2
3
4
source/images/avatar.png
source/images/apple-touch-icon-next.png
source/images/favicon-32x32-next.png
source/images/favicon-16x16-next.png

原来的 URL 没有变化,因此文章和页面不需要修改。

保留“本站已运行”

以前运行时间代码直接写在 NexT 的 footer 模板里,更新主题后很容易丢失。现在通过 NexT 的站点级覆盖文件加载:

1
2
3
custom_file_path:
footer: source/_data/footer.njk
bodyEnd: source/_data/body-end.njk

footer.njk 只保留显示位置,计时逻辑放进独立的 source/js/running-time.js。这样既不会修改 npm 中的主题文件,也可以继续支持 PJAX 页面切换。

遇到的脚本合并问题

升级完成后,页面一开始出现了三个相关现象:

1
2
3
Cannot read properties of undefined (reading 'ignores')
Uncaught SyntaxError: Unexpected token ':'
本站已运行后面没有时间

最后定位到 hexo-filter-optimize 0.3.1 的 JS 合并功能。它会把 NexT 的 application/json 配置块当成普通 JavaScript,导致 Quicklink 配置和页脚脚本被拼接到一起。

解决办法是关闭这个旧插件的 JS 合并:

1
2
3
filter_optimize:
js:
bundle: false

CSS 优化仍然保留,HTML 和 CSS 继续由 Gulp 压缩。关闭后 NexT 会按正确顺序加载自己的配置和脚本,运行时间也可以正常显示。

如果看到 busuanzi 或 Google Analytics 的 ERR_ADDRESS_INVALID,可以先检查 DNS。我的本机当时把两个统计域名都解析到了 0.0.0.0,这属于网络拦截,并不是 Hexo 生成失败。

适配 Gulp 5

原来的 Gulp 配置里包含旧的 CommonJS 插件。升级后将 HTML 压缩插件和图片压缩插件改为动态加载,CSS 压缩改用 gulp-clean-css。默认任务只压缩 HTML 和 CSS,图片压缩单独作为可选任务,避免每次发布都重复处理大量图片。

现在常用命令是:

1
2
3
4
npm run clean
npm run build
npm run minify
npm run deploy

发布批处理也只组合这些 npm scripts,不再直接调用全局 hexogulp

验证结果

最后从空依赖目录执行了完整验证:

1
2
3
4
npm ci
npm run clean
npm run build
npm run minify

结果生成 187 个 HTML,和升级前的路由集合完全一致。首页、文章、归档、标签、关于、404、Atom、站内搜索、百度 sitemap 和普通 sitemap 都存在。

另外还用桌面和移动尺寸检查了首页、文章页、代码块、图片、搜索和 404 页面。NexT Gemini、菜单、头像、favicon、页脚运行时间、阅读进度和返回顶部都正常。升级终于算是完成了。

以后再升级 NexT 时,只需要更新 npm 依赖并检查 _config.next.yml,不需要重新修改主题源码,这应该是这次折腾最有价值的地方。