Hexo中统一图片基础地址的踩坑记录
最近想把博客里大量图片地址统一一下。
原来文章里很多图片都直接写死成了这个前缀:
1 | https://raw.githubusercontent.com/gmwzxiaotaiyang/HEXO_img/refs/heads/master |
这样有两个问题:
- 后面如果图床地址变化,需要全局替换,维护成本高
- 文章里反复出现同样的长前缀,可读性也比较差
所以我打算把它抽成一个统一变量,例如:
1 | {{ IMG_BASE_URL }} |
然后在配置里只维护一份真实地址。
没想到这个改动看起来简单,实际踩了两个坑。
目标
目标很明确:
- 在文章里不再直接写完整图片基础地址
- 改成统一变量:
1 | {{ IMG_BASE_URL }} |
- 真实地址只在配置文件里保留一份,例如:
1 | img_base_url: https://raw.githubusercontent.com/gmwzxiaotaiyang/HEXO_img/refs/heads/master |
这样后续如果要换图床,只需要改一处。
第一步:批量替换源码中的图片前缀
先把 source/**/*.md 里的原始前缀替换成变量:
1 | https://raw.githubusercontent.com/gmwzxiaotaiyang/HEXO_img/refs/heads/master |
替换为:
1 | {{ IMG_BASE_URL }} |
同时在 _config.yml 中增加统一配置:
1 | img_base_url: https://raw.githubusercontent.com/gmwzxiaotaiyang/HEXO_img/refs/heads/master |
再新增一个 Hexo 脚本,在渲染前把占位符替换成真实地址。
最开始的思路是对的,但很快就遇到了第一个问题。
第一个坑:YAML front matter 解析报错
有些文章的封面图写在 front matter 里,例如:
1 | cover: {{ IMG_BASE_URL }}/test/20220214093102.png |
执行 hexo generate 之后,直接报错:
1 | YAMLException: bad indentation of a mapping entry |
类似这种报错:
1 | cover: {{ IMG_BASE_URL }}/test/20220214093102.png |
原因
因为 front matter 本质上是 YAML。
而 YAML 里直接裸写:
1 | {{ IMG_BASE_URL }} |
会被当成非法映射结构处理,不是普通字符串。
解决方式
只要在 front matter 中,把整个值写成带引号的字符串即可:
1 | cover: "{{ IMG_BASE_URL }}/test/20220214093102.png" |
如果是 top_img、avatar 或其他 front matter 字段里用到这个变量,也同理。
结论
- 正文中可以直接写
https://raw.githubusercontent.com/gmwzxiaotaiyang/HEXO_img/refs/heads/master - front matter 中必须加引号
第二个坑:页面里还是没有正确加载图片
YAML 报错修完之后,hexo generate 能跑了。
但是打开页面后发现图片还是加载不出来,HTML 里实际输出的是:
1 | <img class="post_bg ls-is-cached lazyloaded" |
这说明虽然页面已经生成出来了,但变量 根本没有被替换。
浏览器当然不认识:
1 | {{ IMG_BASE_URL }} |
所以图片加载失败,最后触发 onerror,回退到了默认的 404.jpg。
为什么会这样
一开始写的替换脚本,只处理了文章正文里的:
1 | data.content |
也就是说:
- Markdown 正文里的图片链接可以替换
- 但 front matter 中的
cover、top_img等字段没有被处理
而 Butterfly 主题里很多顶部背景图、封面图,正是从这些 front matter 字段渲染出来的。
所以虽然文章能生成,但最终 HTML 里依旧保留了:
1 | {{ IMG_BASE_URL }} |
正确做法:在渲染前递归替换文章对象中的字符串
最终的思路是:
- 在
_config.yml中维护img_base_url - 在
scripts/img-base-url.js中注册 Hexo 过滤器 - 在
before_post_render和before_page_render阶段,对文章对象中的字符串进行替换 - 既处理正文,也处理 front matter 中的字符串字段
但是这里还有一个细节坑:
不能粗暴地改整个对象的所有属性
如果直接递归遍历整个对象并强行回写所有字段,很容易碰到 Hexo 内部对象上的只读属性,例如:
1 | TypeError: Cannot set property path of [object Object] which has only a getter |
这说明某些字段只有 getter,没有 setter,不能直接重新赋值。
更稳的方式
替换逻辑需要满足两个条件:
- 只处理字符串内容
- 只在属性可写时才回写
这样就不会碰到只读属性报错。
最终思路总结
配置文件
在 _config.yml 中增加:
1 | img_base_url: https://raw.githubusercontent.com/gmwzxiaotaiyang/HEXO_img/refs/heads/master |
front matter 写法
1 | cover: "{{ IMG_BASE_URL }}/test/20220214093102.png" |
正文写法
1 |  |
渲染阶段
通过 Hexo 脚本,把:
1 | {{ IMG_BASE_URL }} |
替换为真实地址。
最后如何验证
做完之后,可以执行:
1 | npx hexo clean |
然后再检查生成结果里是否还残留变量:
1 | grep -R "{{ IMG_BASE_URL }}" public |
如果没有输出,说明生成产物里已经没有残留占位符。
另外还可以直接检查页面里的图片链接,确认类似:
1 | data-src="https://raw.githubusercontent.com/gmwzxiaotaiyang/HEXO_img/refs/heads/master/test/20220214093102.png" |
而不是:
1 | data-src="{{ IMG_BASE_URL }}/test/20220214093102.png" |
这次排障得到的结论
这次看起来只是“给图片地址加个变量”,实际踩坑主要集中在两点:
1. YAML front matter 里变量必须加引号
错误写法:
1 | cover: {{ IMG_BASE_URL }}/test/xxx.png |
正确写法:
1 | cover: "{{ IMG_BASE_URL }}/test/xxx.png" |
2. 只替换正文不够,front matter 也要在渲染前替换
否则生成出来的 HTML 里仍然会保留:
1 | {{ IMG_BASE_URL }} |
浏览器无法识别,图片自然就加载失败。
总结
这次改造之后,好处还是很明显的:
- 图片基础地址统一维护
- 后面如果切换图床,只需要改一处配置
- 文章内容更整洁,不用反复写一长串前缀
但同时也要记住:
- front matter 是 YAML,不是普通文本
- Hexo 主题使用的封面图、背景图,很多来自文章元数据,不只是正文
- 变量替换逻辑必须覆盖正文和 front matter 两侧
后面如果再做类似的变量化处理,最好优先考虑:
- 配置入口放哪
- 渲染阶段在哪替换
- YAML 和模板语法会不会互相冲突
这样就能少踩很多坑。





