1944 字
10 分钟
把文章 URL 从目录里拆出来

如果说第一篇写的是“这个博客终于能部署出去了”,那第二篇更像是在处理另一个迟早要面对的问题:文章到底该怎么命名,URL 到底该怎么长。

最开始的做法很省事,文章目录直接按日期建,比如 20260308。这样当然能用,但问题也很快暴露出来了。

第一,一天可能不止写一篇。第二,这个目录名会直接变成文章 URL。第三,纯日期作为链接虽然整齐,但既不直观,也谈不上对长期维护友好。

本地组织内容和对外暴露链接,本来就不是同一回事。之前我把这两件事绑在一起,只是因为项目刚开始,还没有感受到它们分开之后能带来的好处。

等真的开始写第二篇时,这个问题就已经没法继续装作不存在了。

目录名和 URL,不该是同一个东西#

我后来越来越确定,文章系统里至少有两种“名字”:

  • 给作者自己看的内容目录名
  • 给读者和搜索引擎看的访问 URL

这两者的目标其实完全不同。

目录名更适合追求可读性。比如 2026-03-08-折腾这个博客的第一夜,我一眼就知道这是什么、是哪天写的,也方便以后直接在文件系统里找文章。

但 URL 就不一定要背这个包袱。它更重要的是稳定、可控,而且不要因为目录改名或者标题变动就跟着漂移。

所以最后定下来的方案是:

  • 目录:日期 + 中文标题
  • URL slug:单独维护

像今天这两篇文章,目录会是:

2026-03-08-折腾这个博客的第一夜
2026-03-08-把文章 URL 从目录里拆出来

而它们对外的 URL 会是:

20260308-01
20260308-02

这套规则其实很朴素,但它一下子把几个问题都顺手解决了。

不想手动想 slug,那就让 slug 只负责稳定#

一开始我也在纠结,到底要不要用更“像 SEO”的语义化 slug。比如英文关键词、短标题、甚至标题转拼音之类的方案。

想了一圈之后,我反而更清楚自己真正想避免的是什么:

  • 不想每次写文章都额外想一遍 slug
  • 不想为了 URL 去牺牲本地目录的可读性
  • 不想以后改标题时担心链接是否会一起变化

既然如此,那 slug 最适合扮演的角色就不是“语义表达”,而是“稳定编号”。

20260308-01 这种形式有一个很实际的好处:它不需要我额外决策。

写文章时,最稀缺的其实不是生成 slug 的技术能力,而是注意力。能省掉的决策,就应该省掉。让脚本自动给我一个当天递增编号,比让我每次都花几十秒想一个“以后看起来也不会后悔”的链接名字,要合理得多。

有时候,好的工程设计不是提供更多可选项,而是替你拿掉那些其实没有价值的选择。

真正的改造,不是脚本,而是把整条链路都换掉#

表面上看,这次像是在改新建文章脚本。

但只改脚本其实没用,因为这个博客原来的文章路由逻辑本身就把内容目录当作 public slug 在用。换句话说,如果只改了创建目录的规则,不把后面的读取和链接生成一起改掉,最终只是把问题换个地方继续存在。

所以这次真正动到的是整条链路:

  1. 内容 schema 增加 urlSlug 字段
  2. 新建文章时自动写入 urlSlug
  3. 页面静态路由改成按 urlSlug 生成
  4. 上一篇 / 下一篇跳转也跟着用 urlSlug
  5. RSS 输出链接统一切到 urlSlug
  6. 首页和归档页的文章卡片也统一用新链接

这样改完之后,目录改名和 URL 就彻底分开了。

以后我哪怕把目录从中文改成英文、从单层改成多层,只要 urlSlug 不变,站点外部链接就不需要跟着动。对博客这种长期会不断调整结构的项目来说,这一点比它看上去重要得多。

一个小改动,顺手暴露了几个真实的类型问题#

这次改造还有一个很典型的工程副作用:当你开始认真梳理一条链路,项目里原本被忽略的边角错误也会一起浮出来。

我在跑检查的时候,先后碰到了两个问题。

一个是 LightDarkSwitch 在 Astro 里的类型推断异常,导致 client:only="svelte" 这类写法在导航栏里报错。最后回头看,问题并不是导航栏本身,而是组件内部状态写法和项目其余部分的预期并不一致。

另一个是归档页类型定义过窄。内容 schema 允许分类是 null,但归档面板里却把它写成了更严格的 string | undefined,结果父组件一把真实数据传进去,类型系统立刻就不认了。

这类问题的有趣之处在于,它们平时并不一定会把页面直接搞挂,所以很容易长期留在那里。但一旦你开始认真做结构性改造,类型系统就会像放大镜一样把这些不一致都照出来。

说到底,这不是“改着改着改出新 bug”,而是“原来已有的问题终于被看见了”。

我开始更喜欢这种“解耦之后再推进”的感觉#

这一晚做的很多事,表面上都不算大功能。

第一篇是把部署链路理顺。 第二篇是把文章组织和 URL 规则理顺。

它们的共同点是,都不属于那种用户一眼就能看到的“新东西”。页面不会因为这些改动看起来更炫,功能列表里也不会多出什么特别夸张的能力。

但它们会让后续每一步都更顺。

部署从 root 拆出来之后,我知道以后发布边界在哪里。 URL 从目录里拆出来之后,我知道以后改内容结构时哪些地方可以动,哪些地方最好别动。

这种感觉很像把桌面先收拾干净,再开始干活。真正提升效率的,常常不是那一瞬间多做了什么,而是后面每一步都少了很多不必要的摩擦。

今天的第二篇,就记这个系统终于开始像系统了#

如果说第一篇更像“站点上线前的工程收尾”,那这一篇大概就是“内容系统终于开始成型”的记录。

文章目录、URL、路由、RSS、归档、类型定义,这些零碎的东西原本各管各的。现在它们终于围绕一个更明确的规则站到了一起。

以后我写文章时,不需要再手动想 slug,不需要再为一天写两篇而改命名策略,也不需要担心目录一改,外部链接就会跟着变。

这其实不是一次很张扬的升级,但它对一个准备长期写下去的博客来说,刚好是那种足够基础、也足够值得尽早做的事。

系统感这种东西,很多时候不是靠增加复杂度得来的,而是靠把本来就不该耦合在一起的东西拆开。

今天做的,就是这样一次拆分。

把文章 URL 从目录里拆出来
https://blog.yangyus8.top/posts/20260308-02/
作者
杨与S8
发布于
2026-03-08
许可协议
CC BY-NC-SA 4.0