1 分钟阅读
Content collections:让博客不出错的那道防线
类型化的前置元数据听起来像是多此一举,直到某天一个拼写错误让文章悄悄从列表里消失。

有个故障模式我踩过不止一次:你写了一篇文章,页面渲染正常,但它从来没出现在列表里。原因只是前置元数据少了一个字符——date 写成了 published,或者 draft 拼错了。页面照样构建成功,索引查询返回空,一片安静。
Content collections 的做法是把前置元数据从「字符串魔法」变成真正的 schema,从而根治这个问题。
Schema
// src/content.config.ts
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog/en' }),
schema: z.object({
title: z.string(),
description: z.string(),
date: z.coerce.date(),
draft: z.boolean().default(false),
tags: z.array(z.string()).default([]),
}),
});
这样缺了 title 就会带着有用的报错信息构建失败,而不是产出一个标题为空的页面。z.coerce.date() 接受 2026-09-28 并返回一个真正的 Date,排序时不再需要任何猜测。
两种语言用两个集合
我把中英文拆成两个集合,而不是在一个集合里加 lang 字段。配置上多了点重复,页面里却省了很多事:
export const collections = {
'blog-en': defineCollection({ /* ... */ }),
'blog-zh': defineCollection({ /* ... */ }),
};
每种语言的索引只查自己的集合,不存在「忘了过滤」的可能,也不会因为某个人漏写判断条件,导致中文文章出现在英文列表里。
我往 schema 里放什么
只放模板真正会读取的字段。每加一个字段,就意味着每篇文章都要多填一项,而且这个字段还可能和页面实际用法脱节。title、description、date、draft、tags、cover——就这些。阅读时长和摘要都是从正文在构建时算出来的,不存。
我遵循的原则是:如果一个值可以从文章本身算出来,就不要让作者再敲一遍。
astromarkdown类型