给站装一块仪表盘:曝光基本盘、站控室与站内检索

今天做了什么

站里内容涨到 158 个页面之后,我第一次把它当成“要被人找到的东西”来体检。结论是:内容没问题,入口、索引、度量这三层还停在个人主页的假设上。

先做了一次全量取证(都是能指着文件复述的事实,不是感觉):

  • /robots.txt 返回的是首页 HTML——文件根本不存在,搜索引擎拿到一份假 robots。
  • 随便一个不存在的地址(/nope-xyz/)返回 200 + 首页:软 404,等于对外声明“我有无数个内容相同的页面”。
  • sitemap 只有 99 条,四个静态子站(/ai、/baike、/neon-rush、/howtolivebetter)一条都没有——刚搬进来的 45 篇知识库对内页对搜索引擎完全隐形。
  • 全站源码里 canonical 0 处、og:image 0 处、结构化数据 0 处。
  • 站内没有检索,唯一的搜索框搜的是全网。

然后按三批修:

批次 0·曝光基本盘。 robots.txt 改成构建生成(域名只在 astro.config.mjs 定义一次,换域名不用找第二处);新增 404.astro 终结软 404;Base.astro 统一补 canonical(只取路径,丢掉 ?q= 这类状态参数)+ og/twitter 卡片 + JSON-LD(全站 WebSite,文章页 Article / 作品页 CreativeWork);用 Python/Pillow 生成全站默认社交卡片图(1200×630,脚本入库,改文案重跑一次即可);astro.config.mjs 构建时扫一遍 publish/assets/**/*.html,把同域挂载页面补进 customPages——sitemap 从 99 条涨到 156 条。

批次 1·站控室。 新增 site/scripts/site-health.mjs,构建前从源文件算出一份 site-data/health.json;新增 /console/ 页面读它。视觉走配电盘隐喻:同一根母线(域名)上挂主站 + 四个子站回路,每条回路一盏状态灯;右侧是内容层读数(11 个模块的条数 / 最后改动 / 新鲜度灯)和体检(曝光基本盘 7 项 + 内容卫生 7 项)。全程构建期算、零外部依赖、不读 dist。导航不加项(守 ADR-0027),入口放在首页统计条、页脚,和 /workbench 互链——驾驶舱看“站”,工作台看“我”。

批次 2·站内检索 + 内容卫生。 接入 Pagefind:build-search-index.mjs 用 Node API 逐文件登记主站 99 个页面(不用 CLI,因为 --glob 的 ! 否定写法会和 Windows 的 cmd 引号规则打架),产物 dist/pagefind/,新增 /search/ 页面,支持 ?q= 深链、高亮摘要、3 行截断。同域挂载的子应用故意不进索引——它们是客户端渲染的壳,HTML 只有 2KB,索引只会得到一堆空页面;它们各自带检索。

过程与坑

坑一:CF Pages 没有 404.html 时会全局回落到根首页并返回 200。 这条不是今天新踩的(子站深链那次就撞过),但今天才明白它的完整代价:不只是 rewrite 被吃掉,而是所有错误地址都在制造重复内容。修法反而简单——补一张真 404 就结束。顺手确认了不会打坏 /ai 深链:那些是构建期写的真实静态壳文件,静态资源优先级最高。

坑二:标签大小写不一致会裂出两张页,而且两个平台的裂法还不一样。 体检脚本报出 Godot / godot 冲突(一个来自 9 月的日志,一个来自作品页)。本地 Windows 文件系统不区分大小写,两个标签页被写进同一个目录、后一个覆盖前一个,dist 里只剩 10 个标签目录;Linux 上会实打实生成 11 个。于是同一个仓库在本地和线上页面数差 1——这种差异只有把数字写进仪表盘才看得见。

坑三:pnpm analyze 不能挂进 pnpm build。 本来想一劳永逸,读了源码发现它遇到 ~/.codex/sessions 不存在时不报错,而是安静地用 0 覆盖 sessions.json。CF 构建机没有这个目录——挂上去会把线上工作台的会话统计清零。改成“给脚本加无数据源则早退守卫 + 把陈旧检测做进仪表盘”。

坑四:把“最后改动时间”接成文件 mtime 会在云端撒谎。 云端刚 checkout,所有文件 mtime 都是“今天”,新鲜度灯会全绿。改成先读文件自带的日期字段(created / updated / starred),没有再退回该路径的 git 提交日期。

一个诚实但不舒服的数字: 站内互链只有 5 条,62 篇内容里 61 篇没有任何入链。复利的三个齿轮里,“沉淀”勉强在转、“互链”和“回流”基本没动。这件事被写进站控室的体检面板,长期亮着。

下一步

  • 曝光基本盘只剩一件需要人做的事:在 Cloudflare 后台开 Web Analytics(零代码,站控室上会一直亮着一个 ! 直到它开)。
  • 外部读数(访问量 / 被收录页数 / 被引用次数)先人肉读、每周复盘贴一次;自动化(GitHub Actions + token)等密钥卫生那件事清完再说。
  • 内容卫生待办:统一标签引号写法和 Godot/godot;给 踩坑 一级标签补真内容;挑几篇旧文回链新内容,把“入链数”从 5 抬起来。

相关

补记:统计接上了,也交了一笔“自己的学费”

Cloudflare Web Analytics 已开。 走的是 Pages 项目的一键路径(Workers & Pages → 项目 → Metrics → Enable),重新部署后边缘往 HTML 里注入 beacon,站控室那一行当场由 ! 变 ✔——不是我又改了一次状态,是那行代码在页面上自己探测出来的。

同一次检查里,站控室抓到了我自己的 bug,而且抓得很准。 线上面板写着“同域挂载页 5”,AI 知识库显示 0 页 · 断路。原因是我把 site-health.mjs 排在了 build-ai-kb.mjs 之前,而 publish/assets/ai/ 是 gitignore 的构建产物——本地因为有上一次构建的残留,数字看着一直是对的;Cloudflare 上从零开始构建,那 52 页就一条不剩地消失了。

这件事有两层值得记:

  1. 构建链里,产物的生产必须排在消费之前。 任何“读产物”的脚本(体检、sitemap 补全)都得放在“造产物”的脚本之后。这条已经写进记忆库的防熵增军规。
  2. 静默算错比报错危险得多。 那份数字长得完全正常(107 页、5 个子站页),只有一个数字不对。修法不只是调顺序,还给体检脚本加了“子站产物缺失就报警”——下次再排错,它会直接喊出来,而不是安静地少算一半。

顺带把内容卫生清掉了:标签引号统一、Godot/godot 冲突修掉、新增一篇 静态站的入口、索引、度量:一次体检清单(一级标签 踩坑 的第二次使用),并给两篇同日记录补了回链。站内互链从 5 条涨到 19 条,无入链从 62 篇降到 58 篇。

私密侧也补齐了:site-health.mjs 现在同时产出一份 site-data/workbench/site-status.md,Obsidian 的 工作台.md 用 ![[site-status]] 嵌进去——站上看到的和私密侧看到的是同一份数字,外部读数三行留白等人每周手填。

补记二:把“回链”从纪律改成机制

手写回链靠纪律,纪律会忘。所以又加了一件机制性的东西:详情页的相关推荐。时间流、笔记、作品三个详情页的尾部现在都会自动挂 3 条指向旧内容的入口,排序规则三条,全部构建期算得出来:

  1. 共同标签数 ×2 —— 内容真的在讲同一件事
  2. 同类型 +1 —— 时间流更可能指向时间流
  3. 越新越靠前 —— 给老文引流时优先推近作

一条共同标签都没有的,用“同类型最新”补齐,保证每页都有入口。于是这篇日志的页尾自己带出了 08-16 的《时间流 v2》、09-12 的《AI 小镇开工》和当天的《实验室开张》——旧内容从此有稳定的新入口,不用等人想起来去回链。这也是复利第三齿轮第一次不是靠自觉在转。

(手写回链仍然是硬指标:站控室那行同时报“手写互链 X 条 / Y 篇没有手写入链”和“每篇另有 3 条自动相关推荐”,两者不互相抵消。)