半天搭一个笔记发布站本来不算什么稀奇事。让我决定写下这篇短文的,是后半场——一个看似平淡的细节问题,把整个项目从"能用"推到了"清晰"。
起因
~/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 标题这个细节上多问的那一句"是不是应该统一一个规则比较好"。
代码写完只是开始。把"代码暗示的工程哲学"也写下来,下次接新场景才能不掉回去。