跳到正文

一次 Hexo 文章目录结构探索:为什么最后放弃了 index.md

一篇博客要附带两个可公开访问的脚本。继续放在 source/files 里,维护时就要在正文和资源目录之间来回找;放进 _posts 的同名目录,文章列表里又会混入一批资源目录。我想让每篇文章有自己的资源归档,同时保留一个便于扫读的文章列表。

最理想的样子大概是这样:

source/_posts/webstorm-eap-plugin-compat/
  index.md
  scripts/
    force-webstorm-plugin-compat.sh
    force-webstorm-plugin-compat.ps1
  images/
    screenshot.png

这类结构很像很多文档站的「文章包」:根目录只放正文,资源按类型放进子目录。打开目录时,index.md 就是入口;资源一多,也不会跟正文混在一起。

想法很顺,但落到 Hexo 上,就没那么自然了。

第一个目标:URL 不带日期

一开始还想顺手把文章 URL 改得更干净。

旧 URL 是这样:

https://shengsheng.fun/2026/05/29/webstorm-eap-plugin-compat/

如果改成:

permalink: :title/

就可以变成:

https://shengsheng.fun/webstorm-eap-plugin-compat/

去掉日期后,URL 更短;代价是已有外链都要迁移。

个人博客可以大胆试,但 URL 一旦发出去,就不只是本地文件名了。它可能已经贴在聊天、文档、Issue 或收藏夹里。哪怕只有几处,也要么逐个改,要么补重定向。

不过,去掉 URL 日期并不能让正文和资源更好找。公开链接形态与本地目录组织可以分别调整,没必要为了整理源码就迁移已有外链。

第二个目标:文章和资源放一起

Hexo 有一个原生配置叫 post_asset_folder。

它支持这种结构:

source/_posts/foo.md
source/_posts/foo/image.png
source/_posts/foo/tool.sh

文章是 foo.md,资源目录是 foo/。资源会作为这篇文章的 PostAsset 跟着生成,最终可以挂在文章 URL 下面。

这个方案的好处是:它是 Hexo 原生规则,不需要自己写额外脚本。

但它也有一个不舒服的地方:source/_posts 下面会同时出现文章文件和资源目录。

文章一多,目录会变成这样:

source/_posts/
  foo.md
  foo/
  bar.md
  bar/
  baz.md
  baz/

如果每篇文章都有资源,_posts 的根目录就会变得很吵。它不再只是文章列表,而是文章和资源包混在一起。

所以又看向了 index.md。

第三个目标:用 index.md 做文章入口

如果能写成这样,目录会更像一个独立文章包:

source/_posts/foo/
  index.md
  scripts/tool.sh
  images/a.png

这里有两个问题要验证。

第一个问题是 slug。

默认情况下,Hexo 的 new_post_name 是:

new_post_name: :title.md

如果直接把文章写成 source/_posts/foo/index.md,Hexo 可能会把它理解成 foo/index,生成出来的 URL 也会多一段 /index/。

这个可以通过配置绕过去:

new_post_name: :title/index.md

这样 foo/index.md 仍然可以解析出 foo 这个 title,URL 也能维持成 /foo/ 或 /2026/05/29/foo/。

卡住的是第二个问题:资源目录。

Hexo 的 PostAsset 目录按「文章源文件去掉扩展名」计算,不按「文章所在目录」计算。

对应关系是:

source/_posts/foo.md

对应的资源目录是:

source/_posts/foo/

而:

source/_posts/foo/index.md

对应的资源目录会变成:

source/_posts/foo/index/

所以这个结构并不是 Hexo 原生认可的文章资产目录:

source/_posts/foo/
  index.md
  scripts/tool.sh
  images/a.png

原生能走通的反而是这样:

source/_posts/foo/
  index.md
  index/
    scripts/tool.sh
    images/a.png

最终 URL 可以还是:

/foo/scripts/tool.sh
/foo/images/a.png

但源码里多了一层 index/。这层目录只是为了适配 Hexo 的资产计算规则,并不服务人的阅读和维护。

这就有点别扭了。

PostAsset 根目录取决于源文件去掉扩展名

为什么不写一个 Hexo Script

要让 index.md 直接使用同级资源,可以写一个 Hexo Script。

比如在生成时扫描:

source/_posts/foo/
  index.md
  scripts/
  images/

然后把同级 scripts/、images/ 映射到最终文章 URL 下面。

但这个方案会引入一层隐藏逻辑。

源码看起来像普通 Hexo 文章,但实际资源发布规则依赖一个项目自定义脚本。以后本地预览、主题升级、Hexo 升级、别的 Agent 接手、甚至临时换一套生成命令时,都要记得这个脚本存在。

如果只是为了让目录看起来更整齐,就让「源文件放哪里」和「最终 URL 怎么出来」之间多一层自定义映射,并不划算。

当时没有必须使用文章包的需求,我不愿仅为目录整齐增加一套资产扫描和发布规则。后面为保留旧资源 URL 加的 route 映射,是另一次取舍:它只映射普通静态文件路径,没有改成这里设想的文章包模型。

最后为什么回滚

能保留的目录结构有四种,各自需要承担的发布规则不同。

四种资源组织方式,维护成本分别落在哪里

  • foo.md + foo/ 是最直接的 Hexo 原生文章资产方式,但 _posts 会同时出现文章和资源目录。
  • foo/index.md + foo/index/ 也能按原生规则发布,但资源必须多放一层 index/,不能直接放在 index.md 同级。
  • foo/index.md + foo/scripts/ 最符合预想的文章包结构,但需要自定义 Hexo Script 才能稳定发布。
  • 把资源放回 source/files,正文和静态资源分别管理,本地结构如下:
source/_posts/2026-02-26 - WebStorm EAP 插件不兼容时,手动放开 until-build.md
source/files/2026-02-26 - WebStorm EAP 插件不兼容时,手动放开 until-build/force-webstorm-plugin-compat.sh
source/files/2026-02-26 - WebStorm EAP 插件不兼容时,手动放开 until-build/force-webstorm-plugin-compat.ps1

正文按普通文章解析,资源按普通静态文件发布;本地源码用同一套「日期 + 中文标题」关联两者。

公开 URL 也很明确:

/2026/02/26/webstorm-eap-plugin-compat/
/files/webstorm-eap-plugin-compat/force-webstorm-plugin-compat.sh
/files/webstorm-eap-plugin-compat/force-webstorm-plugin-compat.ps1

我最终把资源放回 source/files,让文章和资源留在 Hexo 的普通目录里,不需要额外维护一套文章包规则。后来文章文件名迁移成「日期 + 中文标题」后,资源目录也跟着改成同样的本地命名;公开 /files/<url_title>/... 路径由一个很小的 Hexo route 映射保持不变。

最终仍采用独立的文章与资源目录,公开 URL 继续由稳定的 url_title 承载;不把 index.md 作为默认文章形态。完整内容见目录与资源约定弹窗。

总结

这次探索要解决的是文章与资源的本地归档问题。index.md 可以调整 slug,但 Hexo 按源文件去掉扩展名计算 PostAsset 根目录,同级 scripts/、images/ 因此不能直接成为它的原生资产;要么增加 index/ 子目录,要么维护自定义文章包发布规则。当前需求不足以承担这份成本,最终保留普通文章与 source/files 分开管理,用相同的日期和中文标题关联本地文件,再以小型 route 映射维持稳定的公开资源 URL。源码整理和外链迁移由此可以分别决定。

目录与资源约定

当前目录和资源约定

正式文章继续放在:

source/_posts/YYYY-MM-DD - 中文标题.md

公开资源源码放在:

source/files/YYYY-MM-DD - 中文标题/

如果资源很多,再按需要拆子目录:

source/files/YYYY-MM-DD - 中文标题/images/
source/files/YYYY-MM-DD - 中文标题/scripts/
source/files/YYYY-MM-DD - 中文标题/data/

文章和资源的公开 URL 继续使用稳定的 url_title:

permalink: :year/:month/:day/:url_title/

这能减少旧链接迁移,也符合这个博客过去的 URL 形态。

暂时不启用 post_asset_folder 作为默认资源组织方式,也不把 index.md 作为默认文章形态。

index.md 卡在资产目录的计算方式上,URL 并不是决定放弃它的原因。目前没有必须使用文章包的需求,正文和资源分开管理更合适。