用 pnpm patch 给 Hexo 补上首页隐藏
同一主题连续写了九篇,发布后它们占了首页开头很长一段,其他主题的文章被推到了后面。把篇目集中到一篇总览以后,我希望首页只留下这个入口,其他文章仍能从归档和搜索找到,已经发出去的链接也继续有效。
这件事需要调整首页的文章列表。正文仍然是正常发布的文章,只是不再逐篇占用首页位置。找到 hexo-generator-index 的 hidden: true 配置后,又遇到一个版本差异:GitHub 上已经有实现,npm 最新发布包却还没有。
最终处理很小:给九篇子文章增加 hidden: true,再用 pnpm patch 补上首页生成器里的一行过滤。以下记录基于 2026-10-30、Hexo 8.1.2、hexo-generator-index 4.0.0 和 pnpm 12.11.2。
先确定要从哪里隐藏
总览是一篇正常文章,里面放背景、各篇链接和简短说明。给子文章的 frontmatter 增加布尔值:
---
title: 连载第 1 篇
hidden: true
---
这里用 Hexo 常见的 title 示意;实际项目保留已有标题、网址和其他字段,只增加 hidden。总览不设置这个字段,或者显式设为 false。
这个方案的展示范围如下:
| 入口 | 总览 | 子文章 |
|---|---|---|
| 首页及首页分页 | 展示 | 不展示,也不占分页名额 |
| 归档 | 展示 | 展示 |
| 本站站内搜索 | 收录 | 收录 |
| 原文链接 | 正常访问 | 正常访问 |
hidden 的含义由读取它的插件决定,不是 Hexo 核心对所有入口统一生效的隐私开关。本站搜索生成器读取完整文章集合,因此保留子文章;换成其他搜索插件,也要检查它自己的过滤规则。
例如 hexo-hide-posts 默认会从多个文章列表移除隐藏文章,并添加 noindex。它适合更广的隐藏需求,与这次只调整首页的范围不同。published: false 则属于未发布状态,也不能用来替代首页聚合。
为什么归档和正文可以保留
Hexo 用不同生成器处理首页、归档和文章页。生成器接收 locals 中的站点数据,再返回需要生成的路由和模板数据。官方 Generator 文档分别给出了列表分页和独立文章生成的例子。
首页生成器可以取得一个经过筛选的集合,再做排序和分页。只要不修改全局文章集合,其他生成器仍能读取完整文章。
因此过滤应该放在分页之前。假设每页三篇,前三篇恰好都是隐藏的子文章:先分页、再在模板里隐藏,第一页就会空掉,分页总数也仍按未筛选的数据计算。先过滤再分页,才能让首页条数与实际内容一致。
用 CSS 隐藏卡片也有同样的问题。它只改变浏览器显示,生成器计算的分页和页面内容仍然存在。
已合并的功能为什么装不到
排查时先确认 npm 的发布版本:
pnpm view hexo-generator-index version
本次返回 4.0.0,时间线是:
| 时间 | 事件 |
|---|---|
| 2024-07-09 | 4.0.0 发布 |
| 2024-12-04 | 支持首页隐藏的 PR #125 合并 |
| 2026-10-30 | 查询时 npm 最新版仍为 4.0.0 |
主分支 README 已经说明 hidden: true,但发布包早于这个 PR。只升级到 npm 的 latest,拿到的仍然是不含该功能的版本。
核对发布包时,可以查看当前实际解析的文件位置:
node -p "require.resolve('hexo-generator-index/lib/generator.js')"
这个路径指向当前安装结果;已经应用补丁时,看到的会是修复后的代码。判断发布包是否缺少功能,要与未打补丁的版本对照。
4.0.0 原始发布包的 lib/generator.js 直接使用全部文章排序。上游提交 5cb1ecb增加的处理只有这一处:
- const posts = locals.posts.sort(config.index_generator.order_by);
+ const posts = locals.posts.filter(post => !post.hidden).sort(config.index_generator.order_by);
后面的置顶、分页和模板选择继续使用原实现。本次只补这一行,不复制整个首页生成器,也不改全局 locals.posts。
用 pnpm 保存这行修改
直接编辑安装目录可以临时验证,但重装依赖后会丢失,其他设备和 CI 也拿不到修改。pnpm 自带的补丁功能会把差异保存到项目中,在后续安装时重新应用。
准备编辑副本
先确保项目已经安装依赖,然后执行:
pnpm install
pnpm patch hexo-generator-index@4.0.0
pnpm 会输出可编辑目录,后面修改的应当是这个副本。需要固定工作位置时,可以给同一命令加上 --edit-dir;例如下面这一条替代上面的 pnpm patch:
pnpm patch hexo-generator-index@4.0.0 --edit-dir .patch-work/hexo-generator-index
把 .patch-work/ 加进项目的 .gitignore,避免提交临时副本。这个目录需要不存在或为空。打开其中的 lib/generator.js,只应用前面的那一行 diff。patch 命令说明
生成补丁并登记配置
使用固定编辑目录时,接着执行:
pnpm patch-commit .patch-work/hexo-generator-index
如果使用默认目录,就把参数替换为 pnpm patch 输出的实际路径。patch-commit 比较副本与原始包,默认生成 patches/hexo-generator-index@4.0.0.patch,登记补丁并执行安装。patch-commit 命令说明
这里的 commit 是记录依赖补丁,不是执行 Git commit。仍需自己审查文件并提交到版本库。
pnpm 12.11.2 在本项目的 pnpm-workspace.yaml 中写入了下面的映射;已有其他补丁时,在同一个 patchedDependencies 下增加一项:
patchedDependencies:
hexo-generator-index@4.0.0: patches/hexo-generator-index@4.0.0.patch
这几个文件一起构成可复现的安装输入:
| 文件 | 保存的内容 |
|---|---|
patches/hexo-generator-index@4.0.0.patch |
对原包的具体差异 |
pnpm-workspace.yaml |
哪个包、哪个版本使用哪份补丁 |
pnpm-lock.yaml |
包版本、补丁信息及依赖解析结果 |
这些文件都要追踪,子文章新增的 frontmatter 也要提交。其他设备使用同一份文件安装:
pnpm install --frozen-lockfile
这次改的是包里的 JavaScript 实现,没有修改依赖声明。需要改变包的依赖关系时,应另外评估 overrides 或包钩子,不能把改 package.json 依赖项也当成普通源码补丁。
本次生成的补丁文件快照弹窗保留了完整差异,可对照检查是否多改了文件。
验证文章还在,首页名额已经释放
安装没有报错,只能说明依赖与补丁能够安装,还要检查 Hexo 的生成结果:
pnpm install --frozen-lockfile
pnpm exec hexo generate
修改 frontmatter 后,需要让生成过程重新读取文章。若复用了旧数据库,先处理对应预览的缓存;多个预览或发布任务并行时,使用独立数据库和输出目录,避免清掉另一份正在使用的产物。本站的发布脚本会上传文件,因此这次验收只执行本地生成。
检查范围应当覆盖:
- 首页所有分页只保留该系列总览,其他普通文章正常展示,子文章不占分页名额。
- 归档和搜索索引仍有每篇子文章,标题与 URL 正确。
- 总览的九个正文链接、原文页面、图片和代码工作区资源都能访问。
- 桌面和手机上都能从首页进入总览,再找到各篇。
检查搜索索引时,要看真实条目的标题和 URL。总览正文也会包含子文章链接,仅在 XML 文本中搜到链接,不能证明子文章本身仍是搜索结果。
接入时的历史验收共检查了 133 篇文章:九篇退出首页后,两个首页分页合计 124 个条目;完整归档和站内搜索仍各保留 133 个条目。桌面 1440 与手机 390 宽度都通过,总览九个正文链接正常。验收记录弹窗保留了入口、计数和资源样本。
官方发布后怎样移除
这份补丁针对确切的 4.0.0。以后看到新版本,先检查已发布包是否包含同等的首页过滤,再决定移除;主分支已经合并、README 已更新,都不能代替这一步。
确认新版本包含功能后,在单独的升级改动中移除旧补丁:
pnpm patch-remove hexo-generator-index@4.0.0
这个命令会删除对应补丁文件与 patchedDependencies 项。patch-remove 命令说明
随后把依赖更新到已经核实的目标版本,重新安装并更新 lockfile,再在不使用补丁的产物上重复前面的验证。不要照抄一个尚未发布的版本号,也不要只升级包却留下不会再命中的旧补丁。
各篇的 hidden: true 继续保留,因为移除的是临时实现补丁,文章的首页展示意图没有改变。补丁背景记录也应更新为已移除,并注明由哪个正式版本接替。
什么时候考虑别的方案
从 GitHub 固定提交安装,也能取得这项功能,但会同时采用那个提交对应的整个包。本次只缺上游一行,所以继续使用 npm 发布版本并保存最小补丁。
如果需求发展成自动按系列折叠、分栏目编排或生成专题页,可以使用项目层生成器。它先处理传给首页的数据,再复用或替换原生成流程;归档、搜索和正文仍可各自使用完整集合。原首页生成器会再次排序,复杂顺序不能仅靠提前排好数组;新增卡片布局也需要主题配合。
总结
首页隐藏是文章集合的筛选,应该发生在分页之前。只过滤首页输入,就能保留总览入口,同时让归档、搜索和原文继续承担各自的用途。
pnpm patch 则让尚未进入正式发布包的一行改动成为项目可追踪的安装输入。差异要小,来源和适用版本要明确,安装与页面行为都要验证;官方发布等价实现后,再把这层临时补丁移除。
首页过滤的补丁文件快照
打开原文件 hexo-generator-index@4.0.0.patch
此文件是 2026-10-30 从项目 patches/ 目录保存的只读快照,维护源仍是项目的依赖补丁。它只替换 lib/generator.js 的一行,与 PR #125 的过滤逻辑一致,没有包含页面模板或其他插件修改。
首页聚合接入时的历史验收
打开原文件 home-overview-validation.json
记录来自接入补丁时的隔离 Hexo 产物。检查范围包括全部首页分页、完整归档和站内搜索索引、九篇正文、资源别名,以及桌面和手机上的总览入口。133 与 124 是该次站点快照的计数,不是平台限制。
入口与计数记录
{
"checkedAt": "2026-10-30T04:29:37.240Z",
"patch": "patches/hexo-generator-index@4.0.0.patch",
"patchChangedLines": {
"removed": 1,
"added": 1
},
"overview": "/2026/10/30/esa-pages-journey/",
"hidden": [
{
"title": "ESA Pages 体验之旅(1):为什么选择 ESA Pages",
"url": "/2026/10/29/esa-pages-plans-and-certificates/"
},
{
"title": "ESA Pages 体验之旅(2):部署静态页面和 API",
"url": "/2026/10/29/esa-pages-fullstack-deployment/"
},
{
"title": "ESA Pages 体验之旅(4):小项目的存储选型",
"url": "/2026/10/29/esa-pages-storage-selection/"
},
{
"title": "ESA Pages 体验之旅(3):ESA CLI 的构建配置",
"url": "/2026/10/29/esa-pages-build-platform/"
},
{
"title": "ESA Pages 体验之旅(5):修复 TableStore SDK 的变量声明错误",
"url": "/2026/10/29/esa-pages-sdk-source/"
},
{
"title": "ESA Pages 体验之旅(6):检查 ESA 的运行环境",
"url": "/2026/10/27/esa-runtime-dev-sdk-compatibility/"
},
{
"title": "ESA Pages 体验之旅(8):四次 fetch 限制下的接口设计",
"url": "/2026/10/29/esa-pages-subrequest-budget/"
},
{
"title": "ESA Pages 体验之旅(7):适配 Node.js 依赖",
"url": "/2026/10/29/esa-pages-tablestore-adaptation/"
},
{
"title": "ESA Pages 体验之旅(9):缓存、防刷与日志",
"url": "/2026/10/29/esa-pages-cache-and-operations/"
}
],
"views": [
{
"viewport": 1440,
"homePages": 2,
"homeEntries": 124,
"overviewEntries": 1,
"hiddenHomeEntries": 0,
"archivePages": 14,
"archiveEntries": 133,
"searchEntries": 133,
"childRoutes": 9,
"overviewLinks": 9
},
{
"viewport": 390,
"homePages": 2,
"homeEntries": 124,
"overviewEntries": 1,
"hiddenHomeEntries": 0,
"archivePages": 14,
"archiveEntries": 133,
"searchEntries": 133,
"childRoutes": 9,
"overviewLinks": 9
}
],
"resourceSamples": [
"files/esa-pages-fullstack-deployment/data/verification.json",
"files/nodejs-build-and-distribution/data/verification.json",
"files/esa-pages-subrequest-budget/kits/identity-migration/FILES.json",
"files/better-auth-edge-storage-evaluation/kits/identity-migration/FILES.json"
],
"globalPostCount": 133,
"errors": [],
"deployed": false
}