把 OpenClaw.NET 的 83 篇 Markdown 变成一个网站:完整复盘

十一假期干成了一件小事:把一个 OpenClaw.NET 仓库里的 83 篇中文 Markdown 文档 ,变成了一个能搜、能翻、有暗色主题的网站: https://openclaw.space.mcode.cn 。

上线前我写了个一百来行的校验脚本,一跑------

报出 121 个死链。

那一刻我才明白,把 Markdown 变成网站这件事,80% 的工作量根本不在渲染,而在链接。


一、先泼盆冷水:仓库里什么都没有

很多人的第一反应是:Docusaurus、VitePress、Next.js,随便上一个。

翻遍仓库根目录:没有 mkdocs.yml,没有 docusaurus.config.js,连 package.json 都没有。

结论很明确------要么为了 83 篇文档装一套重型框架,要么自己写一个构建脚本。

我选了后者。工具链极简到有点寒酸:

  • Node 24 跑构建
  • marked 解析 Markdown
  • highlight.js 做代码高亮

就这三个。整个构建脚本 26 KB,校验脚本 3 KB。


二、导航顺序,绝不能拍脑袋

83 篇文档怎么排侧边栏?

如果我图省事按文件名字母排------"快速入门"会排在"安全"后面,"术语表"会出现在"用户指南"前面。第一次点进站点的人,三秒内就走了。

翻文档的时候发现了一篇 SITE_MAP.md,里面有一张「主导航」表,谁在前谁在后写得清清楚楚,还贴心地写了一句:"在将 Markdown 文档转化为文档网站时使用此地图。"

这张表本来就是为今天这件事准备的

直接解析它,一行都不用自己发明。

但这里有个坑。

那张表里有 6 条指向 zh-CN 目录之外 ------README-cn.md、CONTRIBUTING.md、SECURITY.md、测试手册、路线图......

这些不在本次范围内。果断剔除。

但我在构建日志里把它们打印了出来:

我知道我自己丢了什么。

悄悄少几篇,用户不会发现;明明白白列出来,至少你做决定的时候有依据。


三、先写校验器,它比站点本身更值钱

渲染完 84 个页面,我盯着它们看了三秒------肉眼根本看不出问题。

于是写了个校验脚本,规则简单到不像话:

遍历每个 HTML,把所有 <a href> 和 <img src> 抠出来,逐个检查:目标文件在不在?锚点在不在?

第一次跑,抓出 5 类 bug:

① 嵌套页的"文档首页"链接写死了

index.html 没做相对化,14 个子目录页面全部跳错位置。

② 锚点比对忘了 URL 解码

中文标题的锚点是被百分号编码过的,我拿编码后的串去比对未编码的标题 ID------全军覆没,50 多个正常锚点被误判成死链。

③ 指向源码文件的链接压根没处理 ← 这条最要命

文档里有大量 [EndpointHelpers](../src/OpenClaw.Gateway/Endpoints/EndpointHelpers.cs#L83) 这样的链接。在 GitHub 上点,它是能跳的。

但脱离仓库,它就是个死链。121 个。

④ 右栏目录 + 侧边栏高亮,用 .html 去查一份以 .md 为键的索引

结果:目录全空、当前页不高亮。这个 bug 特别阴险------页面看起来完全正常。

⑤ 正文 H1 和模板标题重复渲染

同一个标题出现两次。


四、站外的链接,到底该"诚实"

第 ③ 条最值得展开讲。

这些链接在 Markdown 里长得人畜无害,谁也不会觉得它有问题。只有当你把它单独拎到一个没有仓库上下文的静态站里,它才暴露出真相。

我的处理方式,不是删掉,也不是留个 404,而是:

渲染成一个虚线下划线的文字,鼠标悬停显示它在仓库里的原始路径。

该指向哪,它还告诉你;但它不假装自己能点。

这 252 处站外引用,最后都以这种方式存在。页面上不刺眼,但信息一点没丢。

我越来越觉得,"不假装"是文档工程里被严重低估的品质。


五、静态,不等于简陋

加分项也说一下:

  • 全文搜索 :构建时把 83 篇正文压成 573KB 的 JSON 索引。标题 > 小标题 > 正文加权排序,多关键词 AND 匹配,零后端
  • 代码高亮在构建期做完(6726 个 token),不占浏览器运行时
  • 14 处 mermaid 图走 CDN 渲染,加载失败就优雅退回代码块
  • 右栏目录跟随滚动高亮、暗色主题、窄屏侧栏变抽屉

还有一个 bug 改了两遍才顺手:摘要里混进了标题锚点的 # 号。

抽纯文本时,得先把自动生成的锚点标签删掉,再剥标签。这个 bug 不会让页面报错------它只会让搜索结果莫名其妙。最安静的 bug,往往最难发现。


六、上线,从来不是最后一步

发布前,我停下来问了三个问题:

  1. 一旦部署,就是公网可访问------任何拿到链接的人都能看
  2. 源码会另外上传到私有云存储
  3. 平台目前没有密钥扫描器

第 3 条最要紧。没有扫描器,我就自己先扫一遍------私钥模式、令牌模式、api_key= 赋值,一个都没命中,才敢往下走。

用户点了确认,我才发。

这不是流程洁癖。公开部署是一个不可撤销的外部动作。


七、上线之后,源目录又变了

站点发出去的当晚,我回源目录核对了一眼。

又多了一篇新文档。

integrations/meta-skill-invocation-api.md ------ MetaSkill 调用 API

不只是新增。你还顺手改了两篇:TOOLS_GUIDE.md 和 AUTHENTICATION.md。

"文档会变,网站不会自己变"------这句话当时是感慨,第二天就变成了待办。

顺手发现的一个坑

这篇新文档,SITE_MAP.md 里没有收录(那张表最后一次修改是 10-01)。

按我的构建逻辑,没被站点地图认领的文档会掉进「更多文档」兜底组,用文件名当标签。结果就是侧边栏里出现一条 "integrations / meta skill invocation api"。

不好看,但它是正确的------没人定义过它该叫什么长什么样。

我的处理:在构建脚本的补充表里给它加一条正式归类(集成 → 「MetaSkill 调用 API」)。等哪天 SITE_MAP.md 补上这条,配置可以删掉,解析结果会一致。

重新构建,然后原地更新

流程跑了一遍:重建 → 校验 → 0 死链 / 0 缺资源 → 产物与源一一对应(83/83,无多余、无缺失)→ 覆盖发布。


写在最后

整个过程最值钱的一句话:

别急着渲染,先写校验器。

渲染 Markdown 是把 marked 装上就有的体力活。

难的是让 83 篇各自为战的文档,换了个环境之后依然诚实------该重写的重写,该标注的标注,一个死链都不留。

以及第二句:

别以为"上线"就是"做完"。

我原以为结尾该写一句漂亮的收尾。实际发生的是:发出去当晚源目录又变了,第二天重新构建,原地更新,域名还换了一个。

文档会变,网站不会自己变。

所以这件事不是"做完了",是"开始了"

**你们团队的文档站,站外链接是怎么处理的?更新站点的时候你踩过 URL 的坑吗?评论区聊聊 **