写 Markdown 发文章最怕的不是写不出,而是某个字段写错导致列表页悄悄空了一块,等发现时已经上线两周。解决思路很简单:把 frontmatter 当接口对待,给 schema、做校验、错在构建期。
定义集合
Astro 的内容集合在 src/content.config.ts 里声明。每个集合绑定一个 glob loader 和一个 zod schema:
const posts = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
schema: z.object({
title: z.string().min(1),
date: z.coerce.date(),
summary: z.string().min(10, 'summary 是列表/搜索/OG 的共同来源,必写'),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
为什么 zod 值得引入
- 构建期报错:缺字段、日期格式错、summary 太短,
astro build直接红。错误永远在发布之前,而不是上线之后。 - 类型直达组件:
CollectionEntry<'posts'>的类型自动推导,post.data.tags在编辑器里就有补全。 - 默认值收敛:
draft、toc这类字段给了默认值,写新文章时 frontmatter 可以很短。
slug 也有规矩
URL 里的 slug 必须全小写加连字符,schema 里用正则卡住:
slug: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/).optional()
中文 slug 会被转义成一串百分号,分享出去既难看又容易断。文件名即 slug,frontmatter 里写不写都行,但写了就必须合规。
交付体验
最终的工作流是:往 content/posts/ 丢一个 md 文件,保存,本地预览已经在列表里;字段写漏了,构建立刻告诉你第几行缺什么。“新增一篇文章不改任何代码”这句话,靠的正是这套构建期契约。