Content Collections 把 Markdown 文件夹变成有类型的数据库。半小时学习,每次加字段或改 slug 都回本。
问题背景
没有 Content Collections,Astro 把每个 Markdown 当散文本。有了之后,你得到 schema 校验、自动补全、统一的查询 API。30 篇以上的站,这就是”心里有底”和”一团乱”的区别。
判断标准
- 已经或预期超过 30 篇 Markdown / MDX 文件。
- frontmatter 写错时希望编译期就报错。
- 多个模板要读同一份内容(首页、hub 页、sitemap、RSS)。
- 站点要维护数年而不是数周。
快速结论
2026 年用 Astro 起内容站,第一天就上 Content Collections。后期补,比一开始就学要贵。
实操步骤
- 建
src/content/articles/目录,放几个带 frontmatter 的 MDX 文件:
---
title: "Hello world"
description: "一篇测试文章,验证 content collections 是否工作。"
publishedAt: 2026-05-22
tags: ["intro", "test"]
author: "alice"
---
# Hello
这是正文。
- 在
src/content/config.ts里写 schema。Schema 是契约——任何文件不满足就编译失败:
import { defineCollection, reference, z } from 'astro:content';
const articles = defineCollection({
type: 'content',
schema: ({ image }) => z.object({
title: z.string().min(10).max(80),
description: z.string().min(120).max(160),
publishedAt: z.date(),
updatedAt: z.date().optional(),
tags: z.array(z.string()).min(1),
cover: image().optional(),
author: reference('authors'),
draft: z.boolean().default(false),
featured: z.boolean().default(false),
}),
});
const authors = defineCollection({
type: 'data', // JSON / YAML,不是 markdown
schema: z.object({
name: z.string(),
bio: z.string(),
twitter: z.string().optional(),
}),
});
export const collections = { articles, authors };
- 跑
npm run dev,Astro 会逐文件校验、明确指出错在哪——红字全清掉再继续:
npm run dev
# Could not parse content collection 'articles':
# src/content/articles/old-post.mdx
# description: String must contain at most 160 character(s)
- 在
src/content/authors/加数据文件:
# src/content/authors/alice.yaml
name: Alice Chen
bio: 独立开发者,写内容站相关。
twitter: alicewrites
- 渲染列表页——结果是类型化的,编辑器会补全
entry.data.title:
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
const posts = (await getCollection('articles', e => !e.data.draft))
.sort((a, b) => +b.data.publishedAt - +a.data.publishedAt);
---
<ul>
{posts.map(p => (
<li>
<a href={`/blog/${p.slug}/`}>{p.data.title}</a>
<time datetime={p.data.publishedAt.toISOString()}>
{p.data.publishedAt.toLocaleDateString()}
</time>
</li>
))}
</ul>
- 单篇路由放
src/pages/blog/[...slug].astro,并把 author reference 解析出来:
---
import { getCollection, getEntry } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('articles', e => !e.data.draft);
return posts.map(p => ({ params: { slug: p.slug }, props: { entry: p } }));
}
const { entry } = Astro.props;
const author = await getEntry(entry.data.author);
const { Content } = await entry.render();
---
<article>
<h1>{entry.data.title}</h1>
<p>作者:{author.data.name}</p>
<Content />
</article>
- 生产检查——build 一次确认没有 schema 漏网:
npm run build
# Generating static routes
# ✓ generated 247 routes
容易踩的坑
- 跳过 schema 用裸 Markdown——Content Collections 的全部优势都丢了。
- 为了”过编译”把必填字段写成可选,让脏数据混进来。
- 把图片路径存成字符串而不是用
image()schema helper,导致优化失效。 - 改完 schema 没重启 dev server,老类型还在。
- 把 Content Collections 当成给非技术编辑用的 CMS——他们还是要写 Markdown。
这篇适合谁
做内容多、需要类型安全和可靠性的 Astro 开发者。
这篇不适合谁
10 页以内的小站,schema 成本回不来。
FAQ
- 支持 MDX 吗: 原生支持
.md和.mdx。MDX 文件里 frontmatter 和组件可以共存。 - 能放非文本内容比如 JSON 吗: 可以,用
type: "data"的 collection 放 JSON 或 YAML。适合存作者、标签、配置。 - frontmatter 写错会怎样: 构建直接失败,错误信息明确指向哪个文件哪个字段。这是 feature,不是 bug。
- 怎么迁移已有 Markdown 目录: 把文件挪到
src/content/<name>/,写 schema,跑npm run dev,逐个改到全过再发布。