Astro 主题开发 · 第 5 章:为内容设计 schema
/ 5 min read
本文对应官方文档内容集合(基础用法在本站《Astro 建站实战》第 5 章讲过,本篇聚焦主题视角)。
学习目标:站在主题作者的角度设计内容集合 schema,让“用你的主题”变成“遵守一份清晰的写作契约”。
schema 是主题的契约
建站时,content.config.ts 是给自己写的;做主题时,它是写给所有使用者看的接口文档——使用者照着 schema 写 frontmatter,写错字段会在构建期得到清晰的报错,而不是运行时页面上莫名缺一块。
这份契约的质量直接决定主题的口碑。设计时问三个问题:
- 必填项够少吗?
title、publishDate之外尽量都可省略——省略字段必须有合理默认值或显式分支; - 字段名有歧义吗?
date还是publishDate?cover还是coverImage?一旦发布就不能随便改,起名一步到位; - 报错可读吗? zod 校验失败的信息会直接展示给使用者,加
.describe()说明字段用途。
一个博客主题的典型 schema
import { defineCollection, z } from 'astro:content'import { glob } from 'astro/loaders'
const post = defineCollection({ loader: glob({ pattern: '**/*.md', base: './content/posts' }), schema: z.object({ title: z.string().max(60).describe('文章标题,建议 30 字内'), description: z.string().describe('摘要,列表页与 SEO 使用'), publishDate: z.coerce.date(), updatedDate: z.coerce.date().optional(), tags: z.array(z.string()).default([]).transform( (tags) => [...new Set(tags.map((t) => t.toLowerCase()))], ), coverImage: z.string().optional(), draft: z.boolean().default(false), }),})
export const collections = { post }值得展开的三个细节:
z.coerce.date():接受字符串自动转 Date,使用者不用知道“必须是 Date 对象”这种内部细节;transform做规范化:标签自动转小写、去重——主题替使用者消化脏数据,而不是报错烦他。约束用在校验上,宽容用在规范化上;draft默认 false:缺字段 = 正常发布,符合直觉。
主题作者的三条实践
1. schema 即文档:主题 README 里列 frontmatter 示例,和 schema 保持一字不差的对应。也可以在主题仓库放一个 content/ 演示集,每个字段都用上一次,让使用者抄。
2. 查询封装成函数:不要让使用者在页面里直接写 getCollection('post') 加过滤逻辑——主题提供 getAllPosts()、getPostsByTag() 这类工具函数(含 draft 过滤、排序),schema 改动时只改一处。本站的 src/data/post.ts 就是这个角色。
3. 严格与宽松的权衡:schema 越严格(枚举、正则、max),使用者体验越好、迁移越痛苦;越宽松,主题对脏数据越宽容但布局越容易崩。博客主题的常见折中:核心字段严格(title/date),装饰字段宽松(coverImage、tags)。
踩坑提示
- schema 加了字段但组件没处理
undefined分支——updatedDate是 optional,页面上就得有“没更新过就不显示”的写法,别忘了。 globloader 的 id 会小写化并处理成 slug,文件名里的中文/大写会变——文档里提醒使用者用英文文件名。- 主题升级改 schema 却没写迁移说明,使用者的旧文章构建报错——schema 变更是 breaking change,进 changelog。
练习
- 给你的主题 schema 加一个
lang字段(枚举:zh/en),带默认值。 - 写
getPostsByTag()工具函数,封装过滤与排序逻辑。 - 故意把
publishDate写成“2026/10/05”,观察z.coerce.date()能否救回来;再写成一个纯文本试试报错效果。