zod 做 frontmatter 契约:把文章格式变成构建期错误

Content Collections 的 schema 是唯一真相。标题超 120 字、日期写错、标签写成字符串,都在构建期直接失败,而不是线上少一页。

写文章最容易出的错不是内容,是格式。少一个引号、日期写成中文、标签忘了方括号,后果是页面静默消失,或者渲染出奇怪的东西。

schema 只有一处

src/content.config.ts 是唯一真相。注意 zod 要从 astro/zod 导入,不是 astro:content:

const blog = defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog', retainBody: true }),
  schema: z.object({
    title: z.string().max(120),
    description: z.string().max(300).default(''),
    date: z.coerce.date(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
    featured: z.boolean().default(false),
  }),
});

三条约束各自挡住什么

120 字上限挡住的是「把摘要当标题写」。它同时是 title 标签、og:title 与 JSON-LD 的输入,超长会在搜索结果里被截断。

date 用 coerce 而不是 z.date(),因为 YAML 里它就是字符串;这样 2026-08-13 能过,写成中文日期会被拒。

tags、draft、featured 有默认值,可以完全不写;但一旦写了就必须是数组与布尔,draft: true 加了引号会被拒。

retainBody 与语言

retainBody: true 让 entry.body 保留原文,阅读时长就靠它算:中日韩按字符数、其余按分词,两边相加。语言从 entry.id 的前缀推导,加文章不需要配任何路由。

schema 管不了的部分

它挡不住「slug 用了中文」「三语只写了两语」「description 写成本文介绍」。这些靠撰写规范:交付清单要求三语齐全,因为缺一门语言时,那门语言的列表里就没有这篇。

修改顺序

内容迁就 schema,不是反过来。要放宽约束,先改 schema、再同步文档、最后才动文章。反过来做,早晚会出现「本地能跑、构建炸掉」的提交。

构建期失败的成本是五秒,线上少一页的成本是没人知道少了什么。

← 返回文章列表

评论

…