# 写一个 notepub 不难,难的是别让它知道太多
半天搭一个笔记发布站本来不算什么稀奇事。让我决定写下这篇短文的,是后半场——一个看似平淡的细节问题,把整个项目从"能用"推到了"清晰"。
## 起因
`~/notes/` 不知不觉攒了 27 篇笔记,双语对照、技术翻译、HN 日报截图、沙箱调研,一大堆。本地 Obsidian 翻起来挺好,但想分享给别人看就别扭——开个仓库贴 link 太重,截图发飞书又把读感全砸了。我想要的是一个能让人"点进来就读"的网址。
mdBook 试过,Docusaurus 看过。两个都不识别 `xxx.en.md` / `xxx.zh.md` 这种双语配对约定,HTML 文件混着 markdown 也都没原生支持。我的 HN 日报是直接 HTML 截图源(橙色主题、Top 30 卡片排版),不是 markdown,丢进 Docusaurus 它就当陌生文件忽略了。
所以决定自己写。Go 单二进制 + goldmark + chi + chroma + embed.FS,上午十一点动手,中午十二点半核心代码完成,顺手把 Caddy + systemd + rsync 三件套接上。下午一点五十左右 `kn.dingzhihao.org` 上线,证书自动签发。
## 真正有意思的是后半场
上线之后开始打磨细节。每一个问题听上去都很小,但反过来都在追问同一件事:**这个工具应该知道多少东西?**
**第一刀:首页底部"共 18 篇文档 · 更新于 X 时间"——砍。**
数字对读者不引导也不评估,留着只占视觉空间。这跟之前我决定 README 不硬编码分类清单是一脉的——交给侧栏自动生成的事,就不要在静态文本里再做一遍。
**第二刀:HN 日报标题撞名。**
每天 16:00 cron 自动归档一份 HTML 到 `~/notes/hn-daily/<日期>.html`,但所有归档的 `<title>` 都是同一句"Hacker News 每日精选 Top 30"。一份还好,第二份起侧栏全是同名,根本分不清。
我的第一反应:scanner 加个 `isDateLike` 函数,文件名长得像 `YYYY-MM-DD` 就用文件名当标题,否则照常抽 `<title>`。20 行代码,效果立刻好了。
然后用户问了一句:
> 这样做特殊处理是不是对后续维护会造成困难?是不是应该统一一个规则比较好?
那句话让我停了下来。
`isDateLike` 是典型的特殊化兼容。下次有按小时归档的、按版本号归档的、按 issue 编号归档的,又得加新分支。每加一种命名约定 scanner 都要新增一段领域知识——它本不该知道"HN 归档是个什么东西"。
## 责任放回正确的位置
真正的根因不在 scanner,在 HN 生成器。是它在生成 HTML 时把 `<title>` 写死成固定串没包含日期。让 scanner 替源头补锅,就是在给缺陷做兼容。
修法只有一句话:
> 唯一可识别的标题是内容生产者的责任,不是发布工具的责任。
scanner 删掉 `isDateLike` 整个函数,回归"md 抽 H1,html 抽 `<title>`,抽不到 fallback 文件名"的统一规则。HN 生成脚本改成 `<title>HN 热榜 · {{REPORT_DATE}}</title>`,日期占位符在生成时注入北京日期。
效果一样——侧栏现在显示 `HN 热榜 · 2026-05-25`——但责任放回了应该负责的人。明天起新归档天然带日期,将来接 X 推文归档、博客订阅归档,notepub 这边一字不改自动接住。新场景只需要决定三件事:放哪、文件名怎么取、`<title>` 怎么写。
## 升华
写完这一刀我才看清这个项目的真正形状:
| 维度 | 规则 |
|---|---|
| `.md` 标题 | 抽第一个 `# H1`,抽不到 fallback 文件名 |
| `.html` 标题 | 抽 `<title>`,抽不到 fallback 文件名 |
| 双语配对 | `<slug>.en.md` + `<slug>.zh.md` 自动配对 |
| 子目录 README | 抽到 `DirReadme`,分类名变链接 |
| 根级 README | scanner 跳过,由 `/` 路由渲染为首页 |
scanner 不知道我的目录是关于什么的——笔记、博客、日报、产品文档,对它都一样。任何目录扔进去都按同一套规则渲染,加新分类不用改一行代码。
写工具的时候最大的诱惑就是"我来帮你处理"。一开始觉得"用户多省心啊",时间一长就发现:每一个特殊化都是债,每一处"贴心"都在偷偷让工具变得更脆。
**写一个 notepub 不难,难的是别让它知道太多。**
让 scanner 只做扫文件、抽标题这一件事,把"这是什么文件、要怎么命名、`<title>` 写啥"留给内容生产者——这个项目突然就变得很轻了。
## 后记
整个流程从上午开工到下午把工程哲学也写下来,约莫三个半小时。最值钱的不是那 18MB Go 二进制,是用户在 HN 标题这个细节上多问的那一句"是不是应该统一一个规则比较好"。
代码写完只是开始。把"代码暗示的工程哲学"也写下来,下次接新场景才能不掉回去。