笔记

Astro 内容集合的类型安全实践

如何利用 Astro Content Collections 的 Schema 验证功能,在构建时捕获内容格式错误。

Astro 的 Content Collections 允许为每种内容类型定义 Schema,在构建时验证所有 frontmatter 字段。这意味着格式错误、类型不匹配或缺少必填字段会在构建阶段被捕获,而不是在运行时才暴露。

基本模式

在 content.config.ts 中定义集合和对应的 Schema:

import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';

const postSchema = z.object({
  title: z.string().trim().min(1),
  summary: z.string().trim().min(1),
  publishedAt: z.coerce.date(),
  tags: z.array(z.string().trim().min(1)),
  draft: z.boolean().default(false),
  category: z.enum(['生活', '思考', '技术']),
});

export const collections = {
  posts: defineCollection({
    loader: glob({ base: './src/content/posts', pattern: '**/*.md' }),
    schema: postSchema,
  }),
};

关键设计决策

使用 z.coerce.date()

日期字段使用 z.coerce.date() 而不是 z.date(),因为 Markdown frontmatter 中的日期是字符串。coerce 会自动将字符串转换为 Date 对象,避免手动解析。

使用 z.enum() 限制选项

分类字段使用 z.enum() 限制可选值,而不是 z.string()。这样在构建时就能发现拼写错误或不一致的分类命名。

提供默认值

draft 字段使用 .default(false),这样忘记设置 draft 状态时会自动默认为 false,而不是构建失败。

嵌套 Schema

对于复杂的内容类型,可以嵌套定义子 Schema:

const projectLinkSchema = z.object({
  label: z.string().trim().min(1),
  url: z.url(),
});

const projectSchema = baseContentSchema.extend({
  status: z.string().trim().min(1),
  relatedLinks: z.array(projectLinkSchema),
  technologies: z.array(z.string().trim().min(1)),
});

构建时验证的好处

  1. 提前发现问题:格式错误在构建时就被捕获,而不是等到页面渲染时才暴露。
  2. 类型安全:TypeScript 能推断出内容的类型,提供智能提示和自动补全。
  3. 文档化:Schema 本身就是内容格式的文档,新成员一看就知道需要哪些字段。
  4. 一致性:所有内容都遵循相同的格式标准,不会出现“这篇文章有 category 字段,那篇没有”的情况。

常见陷阱

  • 忘记处理空值:如果某个字段可能不存在,使用 .optional() 而不是假设它一定存在。
  • 过度严格的验证:太严格的 Schema 会导致频繁的构建失败。在严格性和灵活性之间找到平衡。
  • 忽略 draft 状态:确保 getPublicContent 函数正确过滤 draft 内容。

本笔记的状态

这是一个 growing 状态的笔记。随着在更多项目中使用 Astro Content Collections,会继续补充实际遇到的坑和解决方案。

评论