Astro 5 把原本写在 src/content/config.ts 的 schema 拆成了 loader + schema 两个职责。看起来像在折腾,但用过一阵后会发现:它把”内容来自哪里”这件事抽象出来了

旧写法

import { defineCollection, z } from 'astro:content';

const posts = defineCollection({
  type: 'content',  // 必须是文件
  schema: z.object({ ... }),
});

type: 'content' 隐含”内容必须来自 src/content/posts 下的 md 文件”。换数据源(CMS、数据库、API)就要换框架。

新写法

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

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

loader 可以是 globfile,也可以是任意 async 函数。换成 Notion / Sanity / 自己写的 fetch,集合定义不变,下游所有页面代码也不变。

实际带来的好处

最直接的:草稿期可以先写本地 md 文件,正式发布后切到 CMS,路由和组件零改动。

一个小坑

升级时记得把 src/content/config.ts 改名为 src/content.config.ts(在 src 根目录,不在 content 下)。这是 5.0 的位置变化,文档没强调。