TXY
全部文章

用 Zod 管住 Markdown 内容

静态站点最常见的线上事故是 frontmatter 写错。用内容集合加 schema 校验,让错误在构建时就暴露。

静态站点最容易出问题的地方不是组件,而是内容。少写一个字段、日期格式写错、图片路径拼错,页面不会报错,只会安静地显示 undefined 或者空白。等到有人打开链接才发现,往往已经过了几天。

问题的本质

Markdown 的 frontmatter 是一段 YAML,而 YAML 没有类型。构建工具读进来就是一个普通对象,模板里写 post.data.pubdate 和 post.data.pubDate 都能通过,只是其中一个永远是 undefined。

---
title: 我的文章
pubdate: 2026-09-12   # 少了一个大写字母
---

这种错误肉眼极难发现,但类型系统一眼就能看出来——前提是给它一个类型。

内容集合提供的那一层

Astro 的内容集合把「目录里的 Markdown」抽象成带类型的集合,schema 用 Zod 描述:

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

const blog = defineCollection({
  loader: glob({ base: './src/content/blog', pattern: '**/[^_]*.{md,mdx}' }),
  schema: z.object({
    title: z.string().max(80),
    description: z.string().max(200),
    pubDate: z.coerce.date(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});

export const collections = { blog };

三个细节值得注意:

  1. 配置文件是 src/content.config.ts,不是早期版本里的 src/content/config.ts。
  2. 日期必须用 z.coerce.date()。YAML 里的 2026-09-12 可能被解析成字符串,严格的 z.date() 会直接校验失败。
  3. pattern: '**/[^_]*.{md,mdx}' 会跳过 _ 开头的文件,模板文件因此不会被当成真实内容。

让错误在构建时炸出来

schema 只有在读取内容时才生效,所以要把它放进构建流程:

{
  "scripts": {
    "build": "astro check && astro build"
  }
}

astro check 同时做两件事:检查 TypeScript 类型,以及校验所有内容的 frontmatter。字段写错、类型不符、必填项缺失,都会让构建失败。CI 里这一步失败,部署就不会发生,线上永远是能跑通的版本。

经验是:宁可构建失败,也不要静默降级。

别给必填字段加默认值

一个常见的偷懒写法:

title: z.string().default('未命名'),

看起来很稳,实际上把「写错了」变成了「看起来正常」。三个月后你会发现列表里躺着一篇叫「未命名」的文章,而你已经想不起来它本来该叫什么。

默认值只应该给语义上确实可选的字段,比如 draft(默认不是草稿)、tags(默认没有标签)。标题、摘要、日期这类决定内容身份的字段,缺了就该报错。

排序只写一次

另一个容易不一致的地方是顺序。首页取三篇最新文章,列表页取全部文章,如果两处各写一遍排序逻辑,很容易出现首页和列表页顺序不同。

把这类查询收进一个模块:

export async function getPosts() {
  const all = await getCollection('blog', ({ data }) => !data.draft);
  return all.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
}

页面只调用 getPosts(),顺序永远一致,将来要改排序规则也只改一处。

收益

改造之后,写内容的流程变成:复制模板 → 填字段 → 本地构建 → 通过就推送。错误被挡在提交之前,而不是等读者发现。对一个长期维护的站点来说,这比任何视觉优化都重要——内容会一直增加,而检查成本应该保持恒定。

目录6 节