一次 Hexo 文章目录结构探索:为什么最后放弃了 index.md
这次本来想把博客的文章目录结构彻底整理一下。
起因很简单:有一篇文章需要附带两个可公开访问的脚本。如果脚本继续放在单独的 source/files 里,文章和资源会分开;如果脚本放在 _posts 的同名目录里,目录又会慢慢变多。既然迟早要给博客定一个长期规范,不如趁这个机会把文章 URL、文章源码和相关资源一起想清楚。
最理想的样子大概是这样:
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 一旦发出去,就不只是本地文件名了。它可能已经贴在聊天、文档、Issue 或收藏夹里。哪怕只有几处,也要么逐个改,要么补重定向。
URL 可以改,只是它和「文章目录结构」其实是两个问题。
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 的资产计算规则存在的。
这就有点别扭了。
为什么不写一个 Hexo Script
当然,也可以写一个 Hexo Script。
比如在生成时扫描:
source/_posts/foo/
index.md
scripts/
images/然后把同级 scripts/、images/ 映射到最终文章 URL 下面。
但不太喜欢这个方案。
原因不是它写不出来,而是它会引入一层隐藏逻辑。
源码看起来像普通 Hexo 文章,但实际资源发布规则依赖一个项目自定义脚本。以后本地预览、主题升级、Hexo 升级、别的 Agent 接手、甚至临时换一套生成命令时,都要记得这个脚本存在。
如果只是为了让目录看起来更整齐,就让「源文件放哪里」和「最终 URL 怎么出来」之间多一层自定义映射,并不划算。
项目约定最好是能被文件结构直接说明的。
看路径,就知道它会怎么生成。
不需要先知道某个隐藏脚本。
最后为什么回滚
试下来以后,其实选择只剩下几条。
第一条,继续用 foo.md + foo/。
这是 Hexo 原生文章资产方式,最直接。但 _posts 会同时堆文章和资源目录。
第二条,用 foo/index.md + foo/index/。
这也是原生能跑通的方式,但源码结构很反直觉。index.md 旁边的资源不在旁边,而在 index/ 下面。
第三条,用 foo/index.md + foo/scripts/。
这是最想要的源码结构,但它需要自定义 Hexo Script 才能稳定发布。
第四条,把资源放回 source/files。
它不优雅,但很直白。
source/_posts/webstorm-eap-plugin-compat.md
source/files/webstorm-eap-plugin-compat/force-webstorm-plugin-compat.sh
source/files/webstorm-eap-plugin-compat/force-webstorm-plugin-compat.ps1文章继续是普通文章。资源继续是普通静态文件。两边用同一个 slug 关联。
公开 URL 也很明确:
/2026/05/29/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/_posts/<slug>.md公开资源放在:
source/files/<slug>/如果资源很多,再按需要拆子目录:
source/files/<slug>/images/
source/files/<slug>/scripts/
source/files/<slug>/data/文章 URL 继续保留日期:
permalink: :year/:month/:day/:title/这能减少旧链接迁移,也符合这个博客过去的 URL 形态。
暂时不启用 post_asset_folder 作为默认资源组织方式,也不把 index.md 作为默认文章形态。
如果未来真的要重新设计博客结构,也应该先明确接受哪种代价:是接受 _posts 里文章和资源目录混排,还是接受源码里多一层 index/,还是接受自定义 Hexo Script。
在没有这个必要之前,先回到最朴素、最容易解释的方案。
绕一圈也有用
这次看起来像是绕了一圈又回去了。
但这圈不是白绕。
一开始的直觉是:「把文章和资源放进一个目录,应该更容易维护。」
验证之后才发现,对 Hexo 这个具体系统来说,这个直觉只成立一半。目录看起来更像一个文章包,不代表生成模型也会天然配合。
如果为了追求源码目录的漂亮,引入自定义发布逻辑,后面维护者反而要记更多规则。
这次留下来的判断是:
不要只问目录看起来整不整齐。
还要问它是不是顺着工具本身的模型走。
顺着模型,哪怕目录朴素一点,也更容易长期维护。