前言

写 Hexo 文章多了之后, 会遇到一个共同的问题: 报错信息看不出到底哪里错了。

比如 hexo s 突然起不来, 或者构建出来的东西和预期不一样。网上的解决办法五花八门, 试一圈下来问题还在。

这篇不讲具体某个错误的怎么修, 而是讲一套固定的排查顺序 —— 先排除掉最常见、最容易被忽略的原因, 再往深处查。按这个顺序走, 大部分问题在第 1、2 步就能定位。

排查总览

先给个全貌, 后面逐条展开:

步骤 做什么 能解决什么
1 清干净缓存重装 依赖版本残留、node_modules 脏了
2 查依赖树找冲突源 ERR_REQUIRE_ESM、模块找不到
3 换 Node 版本验证 版本错配导致的诡异报错
4 排除插件影响 某个插件引发的连锁错误
5 才动配置 真正的配置问题

顺序很重要。大部分人一开始就去改配置, 结果改了半天, 其实是缓存脏了。

第一步: 清干净缓存重装

为什么必须先做这步

Node 生态有个很坑的地方: 改了配置但不重装, 行为不会变。

node_modules 里装的是旧版本, 缓存里存的是旧的中间结果。这两个不清掉, 你后面所有的排查都是在验证一个错误的环境。

而且如果你之前在 Node 18 下装过依赖, 现在换到 Node 20, 那些原生模块(native module)是按旧版本编译的, 直接拿来用大概率出问题。

标准操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 清缓存和生成产物
hexo clean

# 2. 删掉依赖锁文件和依赖目录
# Linux / macOS
rm -rf node_modules package-lock.json
npm cache clean --force

# Windows PowerShell
Remove-Item -Recurse -Force node_modules
Remove-Item -Force package-lock.json

# 3. 重新安装
npm install

# 4. 验证
hexo g
hexo s

注意 package-lock.json 也要删。它的作用是锁定依赖版本, 但如果你要解决的问题正是”依赖版本不对”, 那这份锁定文件就是问题的固化 —— 必须删掉让它重新解析。

判断: 这步有没有用

做完这一步就好了, 说明问题出在依赖残留。以后遇到构建问题, 直接从这步开始, 能省很多时间。

第二步: 查依赖树找冲突源

如果第一步做完还是报错, 说明是真实的依赖冲突, 得往下查。

找出某个包到底装了几个版本

这是排查依赖冲突最有用的一条命令:

1
npm ls strip-ansi

输出会明确告诉你哪些包依赖了它、装的是什么版本:

1
2
3
4
5
6
7
zsyyblog@
├── hexo@8.1.0
│ └── hexo-util@4.x
│ └── strip-ansi@7.1.0
└── hexo-butterfly-envelope@1.0.13
└── some-old-plugin@0.8.2
└── strip-ansi@6.0.1

看到同一个包同时存在 7.x 和 6.x, 就知道问题在哪了。

ERR_REQUIRE_ESM 怎么定位

如果报这个错:

1
Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported

排查步骤:

1
2
3
4
5
6
# 1. 看报错里的路径, 记下具体是哪个包
# 2. 查这个包在依赖树里的位置
npm ls <包名>

# 3. 查它的版本和模块类型
cat node_modules/<包名>/package.json | grep -E '"version"|"type"'

关键看 "type" 字段:

  • "type": "module" —— ESM, 不能被 require()
  • 没有 "type" 或 "type": "commonjs" —— CommonJS, 可以被 require()

解决办法: 强制降级传递依赖

如果确认是某个包的新版本把老插件搞崩了, 在项目根目录强制装一个旧版本:

1
npm install <包名>@<兼容版本> --save-exact

--save-exact 会把版本号精确锁定(不带 ^), 这样它会作为直接依赖被放在项目根目录, 所有传递依赖都会优先找到它。

Hexo 升级时最常见的组合是:

1
npm install strip-ansi@6.0.1 --save-exact

第三步: 换 Node 版本验证

为什么换版本能定位问题

有些报错很奇怪, 同一个代码在 A 机器上正常, 在 B 机器上就炸。这类问题九成是 Node 版本差异。

Hexo 各版本对 Node 的要求:

Hexo 版本 Node.js 最低要求
6.2+ 12.13.0
7.0+ 14.0.0
7.x 实际推荐 18.0.0
8.0+ 20.19.0

如果你的 Hexo 8 项目跑在 Node 18 上, 很多报错都是版本不对导致的, 而不是配置错误。

用 nvm 管理版本

安装 nvm:

1
2
# Linux / macOS
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

常用命令:

1
2
3
4
5
nvm install 20        # 安装 Node 20
nvm install 22 # 安装 Node 22
nvm use 20 # 切到 Node 20
nvm ls # 查看已安装版本
nvm current # 查看当前版本

Windows 上推荐用 fnm, 体验和 nvm 一致。

换版本后必须重装依赖

这是最容易漏的一步:

1
2
3
4
5
6
7
nvm use 20

# 必须重装, 不能直接用旧依赖
rm -rf node_modules package-lock.json
npm install

hexo clean && hexo s

因为原生模块是针对特定 Node 版本编译的, 换了 Node 不重装, 大概率会报 NODE_MODULE_VERSION 不匹配。

判断: 是不是版本问题

做法很简单: 换到另一个 Node 大版本, 重新完整安装, 看问题是否消失。

  • 消失 → 确定是版本问题, 把版本固定下来
  • 还在 → 版本不是主因, 继续第四步

把版本固定下来

确认可用后, 在 package.json 里声明, 避免以后重装又变:

1
2
3
4
5
{
"engines": {
"node": ">=22 <25"
}
}

装了 nvm 的话, 还可以配一个 .nvmrc 文件, 内容就是版本号:

1
2
echo "22" > .nvmrc
nvm use

第四步: 排除插件影响

前三步做完还报错, 大概率是某个插件的问题。

二分法定位

最朴素但最有效的办法: 逐个禁用插件。

先在 package.json 里把可疑插件注释掉, 重新安装构建:

1
2
3
4
# 禁用后重装
rm -rf node_modules package-lock.json
npm install
hexo clean && hexo g

如果好了, 就是这个插件的问题。再装回来。

更快的办法: 看报错来源

多数情况下报错信息本身就指向了来源, 比如:

1
2
3
Error: Cannot read properties of undefined (reading 'cover')
# ^^^^^^
# 这里读的是 cover 字段, 去 grep 谁在读 cover
1
2
npm ls <报错里的包名>
# 输出会告诉你这个包是被谁引入的
1
2
# 直接在源码里搜
grep -r "<出错的字段名>" themes/ node_modules/<可疑包>/

临时绕过

如果确认是插件 bug 但急着发文章, 可以用垫片绕过, 而不是改插件源码。

比如老插件调用的全局函数在新版里被搬走了:

1
2
3
4
// 在 _config.butterfly.yml 的 inject.head 里加
window.getScript = window.getScript || function(u, a) {
return window.btf.getScript(u, a)
}

用 || 做存在性判断, 这样即使主题以后又把函数放回去了, 这段代码也不会冲突。

第五步: 动配置

到这一步基本可以确定是配置问题了。

Hexo 配置的排查要点:

配置层级要看清

Hexo 的配置有主题配置和站点配置两套, 同时还存在 _config.yml(站点)和 _config.butterfly.yml(主题)的区别。

常见误区: 把站点配置写到了主题配置里, 或者反过来。先确认你改的文件是哪个。

缩进和类型

YAML 对缩进敏感, 这几个错误很常见:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 错: tab 缩进(YAML 不支持 tab)
inject:
head:
- xxx

# 错: 布尔值写成字符串
enable: 'true' # 这是字符串 "true"
enable: true # 这才是布尔值

# 错: 列表项缩进不对
inject:
head:
- xxx # 应该再缩进两格

插件的配置节点可能缺失

老插件升级后, 配置格式可能变了。报错里出现 undefined 时, 优先怀疑配置节点缺失。

一个通用的排查习惯

最后说一个我觉得最有用的习惯 —— 每次排查都记录下来。

我自己的做法是, 每解决一个新报错, 就写成一篇博客:

  1. 报错现象是什么
  2. 根因是什么
  3. 怎么解决的
  4. 参考链接

这样做有三个好处:

  • 下次遇到同类问题, 直接搜自己的文章, 不用重新推导
  • 一年后回头看, 这些文章正好是最有搜索价值的踩坑记录
  • 排查过程被迫理清一遍, 记忆反而更牢

排查本身是隐性的, 写出来才是显性的知识。

小结

一套固定的排查顺序, 记住这五步:

  1. 清缓存重装 —— 排除依赖残留(最常见)
  2. 查依赖树 —— npm ls <包名> 找冲突源(第二常见)
  3. 换 Node 版本 —— 排除版本错配
  4. 排除插件 —— 二分法禁用 + 看报错来源
  5. 最后动配置 —— YAML 缩进、类型、节点缺失

核心原则: 从最常见的原因查起, 不要一上来就改配置。

绝大多数构建问题在第 1、2 步就解决了, 而大部分人恰恰是从第 5 步开始的 —— 于是改了半天配置, 问题根本不在那里。

参考链接: