用 Astro Content Collections 管好文章元数据
让构建期成为你的第一道防线
把文章放成 Markdown 文件后,frontmatter 就是唯一的数据入口。它和文章内容不同步、格式容易手误、也没有数据库的外键约束——所有问题都得靠校验拦下来。
声明 schema
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
subtitle: z.string().optional(),
publishDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
pinned: z.boolean().default(false),
}),
});
coerce.date() 是关键
这一行代码的选择,影响了日常写作的体验:
publishDate: z.coerce.date() // ✅ 接受 '2026-10-11',内部是 Date
publishDate: z.string() // ❌ 接受一切字符串,包括 '去年'
用 z.coerce.date() 时,你可以写 2026-10-11、2026/10/11,甚至某些宽松写法都会被转成真正的 Date 对象。而用 z.string() 的话,类型是字符串,每次格式化都要自己处理,而且拼错格式要到运行时才暴露。
构建期直接报错:
✘ [ERROR] Expected date, received string
这比线上发现「某篇文章的日期显示成 undefined」好得多。
draft 字段配合查询过滤
const posts = await getCollection('blog', ({ data }) => !data.draft);
草稿在本地 npm run dev 可见,构建时自动排除。忘记改 draft 就不会误发布——这是 schema 最实用的一个默认值。
类型自动推导
const posts = await getCollection('blog');
posts[0].data.title // string,完全类型安全
schema 写错一个字,TS 立刻报错。这是 Content Collections 相比”随便读个 YAML 文件”最大的价值。
一点实践建议
summary留作可选字段,不填时从正文自动截断- 标签用
z.array(z.string()).default([]),避免到处写post.data.tags ?? [] - 需要图片时用
image()helper,Astro 会自动做优化和尺寸推导
一个判断标准:如果某个字段你会手滑写错,它就应该被 schema 管起来。手滑是会发生的,工程应该假设它会发生。