在 Hexo 和 Butterfly 中插入自定义静态页面
Butterfly 的页面布局是固定的:顶部导航 + 左侧文章区 + 右侧 widget 侧栏 + 页脚。大多数时候这够用,但一旦想放一个「不像博客文章」的东西——交互式工具、数据看板、带自己一套 UI 的应用页——主题布局反而变成束缚。
先判断:要的是哪一种「自定义页面」
动手之前先对号入座,选错路线会白折腾。
| 路线 | 最终外观 | 内容自由度 | 要改哪些文件 | 适合做什么 |
|---|---|---|---|---|
| A. 原生页面 | 有导航栏、页脚、侧栏 | Markdown + 内嵌 HTML | 只加 source/ 下的文件 |
关于页、友链页、图文介绍 |
B. skip_render 独立页 |
完全由你决定 | 任意 HTML/CSS/JS | source/ + _config.yml 一行 |
单页应用、工具、论文库 |
| C. iframe 壳 | 有导航栏,内容区完全自定义 | 任意 | 两处(A + B 各一份) | 既要站点外壳又要复杂内容 |
| D. 主题自定义 type | 主题内,与站点风格统一 | Pug 模板级 | 改主题文件 | 长期维护的深度定制 |
一句话判断:
- 只是写内容 → A
- 是在做一个应用 → B
- 做应用但舍不得导航栏 → C
- 要跟主题的卡片、配色、响应式完全融为一体 → D(但代价见第 4 节)
路线 A:Hexo 原生页面
最省事,Hexo 原生支持,几乎零配置。
建文件
新建 source/xxx/index.md,路径就是访问路径(/xxx/):
1 | --- |
Butterfly 支持的页面级 front-matter
Butterfly 会读取这些页面级字段来覆盖全局配置:
| 字段 | 作用 |
|---|---|
title |
页面标题(也会写进 <title> 和 og 标签) |
type |
页面类型,决定走哪个模板分支(见第 4 节) |
top_img |
顶部大图;false 表示不显示,会改成一个简单的页面标题栏 |
comments |
false 关闭评论区 |
aside |
控制右侧 widget 侧栏 |
toc / toc_number / toc_expand |
目录相关,页面级覆盖 |
cover |
封面图 |
mathjax / katex |
单页开启数学公式(全局 math.per_page: false 时用得上) |
highlight_shrink |
代码块是否默认折叠 |
这些字段是在
themes/butterfly/layout/下的 pug 模板里读page.xxx,想确认某个字段生效与否,直接在主题目录里搜page.字段名。
实测结果
Deepseek用一个带 iframe 的测试页跑了一遍 hexo generate,输出 14848 字符,特征是:
- ✅ 被主题包裹(
<html data-theme="light">、导航栏、widget 侧栏、页脚都在) - ✅ Markdown 渲染成 HTML(
这是 <strong>Markdown 正文</strong>) - ✅ 原始 HTML 原样通过,
<iframe>完整保留 - ✅
top_img: false生效,变成<div class="page-title">我的页面</div>
要点:.md 文件里可以直接混写 HTML,Hexo 默认的 marked 渲染器不会转义它。 这一点很多人不知道,导致白白去改主题。
接进菜单
1 | # _config.butterfly.yml |
路线 B:skip_render 独立静态页(重点)
这是本文的重点,也是「真·自定义静态页面」的标准做法。
它解决什么问题
写了一个完整的 index.html(自带 <html>、<head>、<style>、<script>),扔进 source/。
默认情况下 Hexo 不会原样拷贝它,而是把它当成一个 page 来渲染——套上主题的 page 布局。结果就是整个 HTML 被塞进主题的 <head> 里,页面彻底报废。
症状很好认,生成结果的 <head> 里会出现主题的东西:
1 | <html lang="zh-CN" data-theme="light"> |
看到 data-theme 和 og 标签出现在独立页里,就是这个病。
放文件
把完整的 HTML 放到一个目录下:
1 | source/ |
加 skip_render
在站点根目录的 _config.yml(不是主题那一个)里:
1 | # Directory |
路径是相对于 source/ 的,用 minimatch 通配符匹配:
| 写法 | 匹配范围 |
|---|---|
'myapp/**' |
source/myapp/ 下的所有文件(含子目录)——最常用 |
'myapp/index.html' |
只跳过这一个文件,同目录的 css/js 仍会被处理 |
'**/*.html' |
全站所有 .html 都不渲染(慎用) |
⚠️ 注意:
skip_render一旦声明就不止影响目录。如果'myapp/**'里的文件同时还需要被其他 Hexo 插件处理(比如搜索索引),它也会被一并跳过。
必须 hexo clean
这一步极其重要,也是最容易浪费时间的地方。
改完 skip_render 直接 hexo generate,你会发现——没有任何变化。
原因是 Hexo 有构建缓存,它认为文件内容没变,直接复用了上次渲染的结果。必须清一次:
1 | npx hexo clean && npx hexo generate |
第一次遇到时反复检查配置,一度以为 skip_render 的写法不对。实际上配置是有效的,只是缓存没失效。
经验:以后凡是改了
_config.yml里跟渲染路径相关的配置(skip_render、include、exclude、ignore),一律clean后再generate,省得怀疑人生。
验证确实是「原样拷贝」
直接用哈希比对:
1 | if ((Get-FileHash 'public\myapp\index.html').Hash -eq (Get-FileHash 'source\myapp\index.html').Hash) { |
1 | # Linux / macOS |
实测数据:一个 143 字节的测试页,源文件与输出文件哈希完全一致,输出里既没有 data-theme 也没有导航栏——确认原样拷贝。
接进菜单
1 | # _config.butterfly.yml |
注意写 /myapp/(带尾斜杠),让服务器去找目录下的 index.html。写成 /myapp 在某些 Nginx/静态托管配置下会 404 或触发一次重定向。
资源路径怎么写
强烈建议在独立页里全部用绝对路径:
1 | <!-- 推荐 --> |
原因:相对路径的解析结果取决于当前 URL。/myapp/ 和 /myapp/index.html 这两种访问形式对相对路径的解析在部分服务器上不一致;如果哪天页面被嵌到别的路径下(比如用 iframe),相对路径会全部失效。绝对路径没有这些问题。
如果你的站点部署在子目录(_config.yml 里 url 带了路径前缀),绝对路径要加上那个前缀。
路线 C:iframe 壳
适用场景:想要 Butterfly 的导航栏和页脚(用户能点回博客),但内容区是一个完全自定义的应用。
做法
两件事:
- 用路线 B 生成真正的应用页
/myapp/ - 用路线 A 建一个壳页面
source/app/index.md把它嵌进来
1 | --- |
aside: false 去掉右侧 widget,给内容腾出整幅宽度。
让 iframe 高度自适应
固定 height 在不同屏幕上要么留白要么出现内部滚动条。用一点 JS 让 iframe 高度跟随内容:
1 | <iframe id="app-frame" src="/myapp/" style="width:100%;border:none"></iframe> |
同源才能读取
contentDocument。如果 iframe 指向的是外部域名,只能退回固定高度或vh。
三条要提前知道的局限
- SEO 很差。 iframe 里的内容通常不被搜索引擎当作父页面内容索引。如果这个页面的内容需要被搜到,别用 iframe。
- 深色模式不同步。 主题切到暗色时,iframe 内部是独立文档,不会跟着变。要么在应用页里自己读
prefers-color-scheme,要么用postMessage通知它。 - 滚动体验割裂。 页面滚动条和 iframe 内部滚动条容易打架。上面的自适应高度脚本能缓解,但内容里有 sticky 元素时会比较难办。
路线 D:主题自定义 page type(进阶)
Butterfly 的 page.pug 里有一个类型分发:
1 | // themes/butterfly/layout/page.pug |
所以可以自己接一个类型进去:
- 新建
themes/butterfly/layout/includes/page/mytype.pug - 在
page.pug的case里加一个when 'mytype'分支 - 页面 front-matter 写
type: mytype
这样能拿到主题的卡片样式、配色变量和响应式布局,观感最统一。
但是有个大坑
Butterfly 通常是作为 git submodule 引入的。改主题文件意味着:
themes/butterfly变成了「有本地未提交改动」的状态- 升级主题时容易产生冲突,或者被
git submodule update直接覆盖 - 服务器端构建时如果没有同步你的主题改动,效果和本地不一致
如果要走这条路,正确做法是把主题 fork 下来,用自己的 fork 当 submodule,而不是直接改 submodule 里的文件。参考另一篇文章《网站部署问题总结:子模块、版本分歧与同步》里踩过的坑。
建议:能用路线 A/B/C 解决的,就不要动主题。真正需要 D 的场景比想象中少得多——很多时候只是想要个卡片背景,用路线 B 自己写几行 CSS 更快。
补充:全站注入 inject
Butterfly 还提供了一组 inject 配置,把代码插到所有页面的 </head> 前和 </body> 前:
1 | # 主题的 _config.yml(或复制到站点根目录的 _config.butterfly.yml 里覆盖) |
主题模板里的实现位置在 layout/includes/head.pug 和 layout/includes/additional-js.pug。
适用:给全站加统计脚本、额外样式、悬浮挂件。
不适用:整页自定义——它只是往现有页面里塞片段,不会替换布局。
避坑清单
| 现象 | 原因 | 解决 |
|---|---|---|
| 独立页被套进了主题布局 | 忘了配 skip_render |
在站点根目录的 _config.yml 里加,注意是相对 source/ 的路径 |
改了 skip_render 但没效果 |
Hexo 构建缓存 | npx hexo clean && npx hexo generate |
独立页里 --- 那几行显示成文本了 |
给 skip_render 的文件写了 front-matter |
独立页会被原样拷贝,front-matter 不会被剥离,删掉它 |
source/xxx/ 目录下的文件没进 public/ |
目录名以 _ 开头,Hexo 直接忽略 |
改名,或改用 include 配置 |
| 页面里的 css/js 加载 404 | 用了相对路径 | 改成绝对路径 /myapp/style.css |
访问 /myapp 报 404 |
少了尾斜杠 | 用 /myapp/ |
| iframe 高度不对、有内部滚动条 | 固定 height | 用第 3.2 节的 height 自适应脚本 |
| 站点开了 pjax 后自定义页表现异常 | pjax 接管了导航 | 在 pjax.exclude 里排除该路径;或者像本博客一样 pjax.enable: false |
关于 _ 开头的目录
这条值得单独说:Hexo 会忽略 source/ 下所有以 _ 开头的目录。_posts、_drafts、_data 是 Hexo 自己用的特殊目录,其他 _xxx(比如 _draft/)会被完全跳过。
把没写完的草稿放在 source/_draft/,这样它们绝不会被误发布
速查表
1 | # 站点根目录 _config.yml |
1 | # _config.butterfly.yml |
1 | <!-- source/app/index.md —— 路线 C 的壳页面 --> |
1 | # 改完配置后一定要清缓存 |
1 | # 确认是原样拷贝 |
小结
- 只用一个配置项(
skip_render)就能让 Hexo 别再插手页面。 - 改完配置**必须
hexo clean**,否则会以为配置没生效。 - 能用 A/B/C 解决的就别改主题,尤其是主题以 submodule 方式引入的时候。
- 独立页里一律用绝对路径引用资源,能避掉一整类「本地正常、线上 404」的问题。


