用 Astro 内容集合管理你的博客
从 glob loader 到 schema 校验,一步步理解 Astro 内容集合的运作方式,并学会用它组织文章、标签与归档。
内容集合(Content Collections)是 Astro 组织内容的核心方式。它把”内容”和”渲染”分离:你只管写 Markdown,剩下的类型检查、查询、排序都交给框架。
集合与 Loader
在 src/content.config.ts 中,一个集合由 loader 和 schema 构成:
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const post = defineCollection({
loader: glob({ base: './src/content/post', pattern: '**/*.{md,mdx}' }),
schema: z.object({
title: z.string(),
description: z.string().default(''),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
pinned: z.boolean().default(false),
}),
});
export const collections = { post };
glob loader 会把目录下所有匹配的文件变成集合条目,每个条目的 id 就是相对路径(不含扩展名),这也是文章 URL 的一部分。
为什么需要 Schema
Schema 的价值在写作时就能体现出来:
- 默认值:
description缺省为空字符串,不必每篇都写 - 类型推导:
pubDate是Date,排序时可以直接比较 - 错误反馈:漏字段、写错类型,构建时立刻报错,而不是上线后才发现
// 查询时过滤草稿
const posts = await getCollection('post', ({ data }) => !data.draft);
// 按日期排序
posts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
渲染与标题提取
拿到集合条目后,用 render() 得到渲染后的内容和标题结构:
const { Content, headings } = await render(post);
headings 是 { depth, slug, text }[],目录组件就是靠它生成的。注意 slug 由 Astro 根据标题自动生成,所以要保证标题唯一。
常见模式
| 需求 | 做法 |
|---|---|
| 标签页 | 遍历所有文章,countTags() 聚合 |
| 归档 | 按 pubDate 的年份 groupByYear() 分组 |
| 上下篇导航 | 排序后取当前文章的前后邻居 |
| 搜索索引 | 对 stripMarkdown(body) 建立关键词索引 |
这些都是本主题已经实现的功能,可以直接在 src/pages/ 下翻看源码。
性能注意
内容多时,render() 只应在文章页调用;列表页只需 post.data 和 post.body 的纯文本,避免无谓的 Markdown 渲染开销。这个主题首页的阅读时长就是这样算的:
export function readingTime(md: string, wpm = 300): number {
const text = stripMarkdown(md);
const cjk = (text.match(/[\u4e00-\u9fa5\u3040-\u30ff]/g) ?? []).length;
const words = text.replace(/[\u4e00-\u9fa5\u3040-\u30ff]/g, ' ').split(/\s+/).filter(Boolean).length;
return Math.max(1, Math.round(cjk / wpm + words / 220));
}
中文按字、英文按词,比单一 WPM 估算更贴近真实阅读时长。