过了 50 篇文章,Astro 内容站就会冒出看不见的漂移:跨语种重复的 urlSlug、指向空的 translationKey、页面改名后没人管的死内链、明明 draft: true 却照样上线的文件。手动逐个文件读既不可扩展,也没法重复跑。这篇教你用 AI 把 frontmatter 规则写成一个 audit-content.mjs 脚本,按严重度分批修,再把脚本接进 prebuild,让同样的漂移下次部署再也回不来。
一句话总结
- 让一个真正能读文件的 agent(Claude Code 或 OpenAI Codex CLI)读 5 到 10 个 MDX,写出你的 frontmatter 契约。
- 把契约变成一个
scripts/audit-content.mjs,用gray-matter解析 frontmatter,标四类问题:缺必填字段、同语种内urlSlug重复、translationKey配对断裂、死内链。 - 让脚本在硬问题上
process.exit(1)、软问题只 warn,然后跑node scripts/audit-content.mjs小批量修。 - 把脚本加进
package.json的prebuild,这样坏页面到生产之前npm run build就先失败。
这篇讲什么
这里的产出不只是一份问题清单,而是一个每次 build 都跑的脚本,加一份按严重度排好的修复计划。在 Astro 5 和 6 里,内容放在由 src/content.config.ts 定义的 collection 中(这个文件在 Astro 5.0 从 src/content/config.ts 改名而来),用内置的 glob() loader 加载,每个条目暴露的是 id 而不是旧的 slug。你的审计直接读 .mdx 原文,完全不依赖 Astro 的运行时。
几个关键词:
- Frontmatter 契约:每个内容文件必须带的字段(
urlSlug、translationKey、lang、draft、category等),以及每个字段允许的取值。 - 漂移:新加的内容慢慢违反你半年前定下的契约。没有强制检查,它一定会发生。
- BLOCKER 与 WARN:BLOCKER 会让 build 挂掉(重复 slug、缺必填字段);WARN 只记录但照样发(翻译还在做、
translationKey暂时配不上)。
这篇适合谁看
Indie 开发者和内容工程师,跑着超过 50 篇文章的 Astro 内容站——尤其是双语 / 多 locale 站,每对翻译都会引入新的不变量要验证。如果你的仓库已经有 prebuild,这套东西可以直接接上去。
什么时候适合用
上线前。content schema 重构后。每个季度。从别的 CMS 迁内容之后。任何要证明”加内容没把已有的弄坏”的时候。文章级审计跑干净后,把同一套打法往上挪一层,对照 AI Category 页审计教程——Category 页和 Tag 页是大多数团队忽略的面,会悄悄拖垮抓取效率。
选对 AI agent
这套流程靠的是能就地读写文件的 agent,而不是一个你往里粘贴的聊天框。截至 2026 年 6 月,两个实用选择:
| Agent | 模型 | 文件访问 | 编辑模式 |
|---|---|---|---|
| Claude Code | 仅 Anthropic(Sonnet 4.6 / Opus 4.7) | 整个项目树,写入前需批准 | 先出计划,批准后打补丁 |
| OpenAI Codex CLI | GPT-5.5 | 沙箱限定在当前项目目录 | Suggest(默认)/ auto-edit / full-auto |
两者都是终端 CLI,都会读一个项目记忆文件(Claude Code 是 CLAUDE.md,Codex 是 AGENTS.md),你可以拿来固定 frontmatter 契约。如果你常驻编辑器,Cursor 的 agent 模式也行。纯聊天模型理论上也能用,但复制粘贴的成本会把”可重复审计”这件事本身的意义抵消掉。这个 agent 怎么延伸到技术 SEO 检查,见 Claude Code SEO 审计教程。官方文档:Claude Code 和 Codex CLI。
开始前准备
- 工作树要干净(
git status没有改动)。有几类修复是批量改名,你要随时能git revert。 - 确认本地 build 能跑。Astro 5+ 上审计要在
prebuild里跑,先得有绿色基线。如果规模大到 build 爆内存,把堆调大:NODE_OPTIONS=--max-old-space-size=8192 npm run build。 - 装一次 frontmatter 解析器:
npm i -D gray-matter。它能处理 YAML 的边角情况(多行字符串、列表),这些是手写正则会漏掉的。
具体步骤
- 数清面有多大。跑
find src/content -name "*.mdx" | wc -l,留一个数字,后面用来校验脚本。 - 抽出契约。让 agent:“读
src/content下随机 8 个 MDX,写出 frontmatter 契约——哪些字段必填、哪些可选,以及lang、category、subcategory各允许什么值。” 把结果贴进CLAUDE.md/AGENTS.md。 - 收紧契约。agent 只看了样本,会把一些必填字段标成可选。把实际必填的提回来(
urlSlug、translationKey、lang)。 - 用这段 prompt 生成审计脚本:
写 scripts/audit-content.mjs(ESM)。遍历 src/content/**/*.mdx,
用 `gray-matter` 包解析每个文件的 frontmatter,并标出:
- 按上面契约缺必填字段 -> BLOCKER
- 同 lang 内 urlSlug 重复 -> BLOCKER
- translationKey 一个 lang 有、对应 lang 没有 -> WARN
- 非 WIP 文件上的 draft: true -> WARN
- 内链(/en/articles/<slug>/ 或 /zh/...) -> BLOCKER
解析不到已有的 urlSlug
每行一条发现,前缀严重度,末尾一个汇总块。
有任何 BLOCKER 就 process.exit(1),否则 exit 0。
- 跑起来并捕获输出:
node scripts/audit-content.mjs | tee audit.txt。 - 按根因分组。把
audit.txt喂回去:“这是 N 条发现,按根因分组,每组提最小修复。” 一个改名的页面常常一口气解释掉十几条死链发现。 - 分批打补丁。每个 BLOCKER 组让 agent 写真实改动——frontmatter 编辑、文件改名或链接重写——每批之后重跑脚本。如果计数不降反升,立刻停。
- 接进 build。在
package.json里加:
"scripts": {
"audit:content": "node scripts/audit-content.mjs",
"prebuild": "node scripts/audit-content.mjs"
}
这样未来任何漂移都会在坏页面生成之前让 npm run build 非零退出。
第一次实操怎么跑
- 跑审计但什么都先别修。冷读一遍报告。
- 挑出现最多的那一条(通常是
translationKey不匹配或urlSlug重复)。只修这一类,然后重跑。 - 确认计数恰好降了你修的那么多。没降就说明脚本有 bug——先修脚本,再动内容。
- 故意种一个已知坏案例:临时把一篇已发布文章设成
draft: true,确认审计抓到了。没抓到,说明规则没在检查你以为的东西。
完成后检查
- 一条被标的发现能用手工
grep复现吗?如果grep那个 slug 和脚本说的对不上,规则里有误报。 - 多次运行结果稳定吗?顺序随机没关系,数量随机就是 bug。
- 够快能上
prebuild吗?1,000 个文件 3 秒内算正常——Astro 5 的 content layer 让 Markdown 构建快了最多 5 倍,但你的审计跑在它之外,所以 I/O 要尽量精简。 - 汇总块里”看到的文件总数”和
find ... | wc -l对得上吗?对不上说明 glob 漏了文件。
怎么复用这套流程
- 把
scripts/audit-content.mjs入版本控制。别每次重生成——演进它。 - 发现新漂移模式就增量加规则(孤儿 tag、缺
hreflang、description 过长)。脚本会随站的生命周期越攒越值。 - 大批量加内容之前也跑一次,不只是之后跑,让你从已知干净的状态出发。
- frontmatter 和 slug 收拾干净后,再叠一层按你技术栈生成的 SEO checklist,让 render mode、hreflang、schema 这些项跟内容一起被检查。
容易踩的坑
- **靠人工逐文件读。**会漏,而且下季度跑不动。
- **一次把所有修复都应用了。**build 挂了你说不清是哪条修的。分批,每批后重跑。
- **跳过
prebuild接线。**下个月同样的漂移又回来,因为没东西在防它。 - **不给例子就让 agent 写规则。**给它 3 到 5 个已知坏文件;从真实案例长出来的规则更准。
- **把每条 WARN 都当 BLOCKER。**你永远发不了。翻译还在进行中是 WARN,不是 build 失败。
- **没 commit 就重跑。**未提交状态会把下一轮分组搅乱。
FAQ
- 哪种 AI 最适合这件事?:Claude Code(Anthropic 模型)或 OpenAI Codex CLI(GPT-5.5),两者都有直接的文件访问。Codex 默认把写入沙箱限定在项目目录里;Claude Code 每次写入前会请求批准。纯聊天模型也能用,但复制粘贴成本会侵蚀掉整套流程。
- Astro 5 和 6 都能用吗?:能。审计读的是
.mdx原文,跟 Astro 运行时无关。只要记住配置文件是src/content.config.ts(Astro 5.0 改的名),条目暴露的是id而不是旧的slug。 - 双语站怎么处理?:每个
lang各跑一遍做 slug 唯一性检查,最后再跑一遍配对检查,断言每个/en里的translationKey在/zh都有对应。翻译还在做时缺对应算 WARN,已上线就算 BLOCKER。 - 非 MDX 内容呢?:同流程,换 glob。JSON 内容文件、纯 Astro Markdown、YAML 数据文件,都受益于”契约 → 脚本”的做法——把
gray-matter(或JSON.parse)指向对的文件即可。 - 多久跑一次?:通过
prebuild每次 build 都跑。大批量导入内容前手动跑一次,content schema 重构后也跑一次。 - 1,000+ 文件时审计脚本爆内存。:流式读文件清单、逐个读文件,别把所有正文一次性加载进内存;或者把审计按 category 分批跑。把 Node 堆调到
--max-old-space-size=8192只是权宜之计,不是根治。