返回知识教程

TUTORIAL 01 · 工程实践

从零搭建 Astro 内容集合:字段、校验与路由生成

用一个可运行的最小项目走通 Astro 内容集合,从目录结构到 Schema 校验,再到根据内容自动生成详情路由。

内容集合示意:文档、图表与校验标记
5 STEPS · 30 MIN
方向
工程实践
难度
进阶
步骤
5 步
动手用时
约 30 分钟
更新
2026.10.24

这个教程要做什么

用 Astro 的内容集合(Content Collections)搭一个最小可用的内容站骨架:定义 Schema、写两篇内容、让字段校验生效、最后根据内容自动生成详情页路由。

跑完之后你会有一个能构建、能校验、能出页面的项目。

前置条件

  • Node.js 22.23.4 或更高(Astro 7 的硬性要求)
  • 包管理器:npm / pnpm 均可

先确认版本:

node -v
# 应该输出 v22.23.4 或更高

第一步:创建项目

npm create astro@latest my-content-site
cd my-content-site

安装过程中选择:

  • 模板:Empty project(不要选 blog 模板,我们从零建)
  • TypeScript:Yes, strict
  • 安装依赖:Yes
  • 初始化 Git:可选

启动开发服务器确认环境正常:

npm run dev

浏览器打开 http://localhost:4321,看到 Astro 的欢迎页就说明环境没问题。

注意: 如果 Node 版本低于 22.23.4,这一步会直接失败并提示版本不满足。

第二步:建立内容目录

在项目根目录创建内容文件夹:

mkdir -p content/articles

这里有个容易踩的点:内容目录不一定要放在 src/ 下。放在项目根的 content/ 也是可以的,只要在配置里写对 base 路径。这样做的好处是内容与代码分离更彻底。

写第一篇内容 content/articles/hello.md:

---
title: 第一篇文章
description: 这是一篇用于验证字段校验的示例内容,用来确认 Schema 是否按预期工作。
publishedAt: 2026-10-24
status: published
category: 测试
---

## 正文标题

正文内容写在这里。

第三步:定义集合与 Schema

创建 src/content.config.ts:

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

const articles = defineCollection({
  loader: glob({ base: './content/articles', pattern: '**/*.md' }),
  schema: z.object({
    title: z.string(),
    description: z.string().min(20).max(200),
    publishedAt: z.coerce.date(),
    status: z.enum(['draft', 'published']),
    category: z.string(),
    tags: z.array(z.string()).default([]),
  }),
});

export const collections = { articles };

几个关键点:

  • glob() 的 base 是相对项目根的路径,不是相对 src/。
  • z.coerce.date() 会把 Markdown 里的日期字符串自动转成 Date 对象,不需要手写转换。
  • .default([]) 让 tags 可以省略,取不到时是空数组而不是 undefined。

第四步:验证校验真的生效

这是整个教程最重要的部分。不要只是配置完就假设它work了,要主动制造错误看它报不报。

把 hello.md 的 status 改成一个不存在的值:

status: publishedd

运行构建:

npm run build

你应该看到类似这样的错误:

InvalidContentEntryDataError: articles → hello data does not match collection schema.
status: Invalid enum value. Expected 'draft' | 'published', received 'publishedd'

看到这个错误说明校验生效了。 把它改回去,再试一次删掉 description 字段,应该会得到长度不足的错误。

这一步不能跳过。 很多人配置完 Schema 就认为”字段校验已实现”,但从来没验证过它会不会真的拦下来。校验没验证过,等于没有。

第五步:根据内容生成详情路由

创建 src/pages/articles/[slug].astro:

---
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const articles = await getCollection('articles', ({ data }) => {
    return data.status === 'published';
  });

  return articles.map((entry) => ({
    params: { slug: entry.id },
    props: { entry },
  }));
}

const { entry } = Astro.props;
const { Content } = await entry.render();
---

<article>
  <h1>{entry.data.title}</h1>
  <time datetime={entry.data.publishedAt.toISOString()}>
    {entry.data.publishedAt.toLocaleDateString('zh-CN')}
  </time>
  <Content />
</article>

两个要点:

  1. getCollection 的第二个参数是过滤器。 这里过滤掉了 draft,从路由层面保证草稿不会被生成页面——这比在模板里写 if (draft) return null 更可靠,因为草稿根本没有被构建出来。
  2. entry.id 来自文件名。 hello.md 生成的 id 是 hello,所以页面路径是 /articles/hello/。如果想自定义,在 frontmatter 里加 slug 字段。

访问 http://localhost:4321/articles/hello/ 验证页面正常渲染。

常见问题

构建报 Cannot find module 'astro:content'

检查 tsconfig.json 是否 extends 了 astro/tsconfigs/strict 或 strictest。内容集合依赖 TypeScript 的严格配置。

日期显示时区偏移一天

z.coerce.date() 解析 2026-10-24 这种纯日期字符串时会按 UTC 处理。如果按本地时区显示,可能差一天。建议在展示层统一指定时区,或者干脆在 frontmatter 里写完整时间戳。

改了内容但页面没更新

开发模式下内容集合有缓存。修改 content.config.ts 后需要重启开发服务器。

完整文件结构

my-content-site/
├── content/
│   └── articles/
│       └── hello.md
├── src/
│   ├── content.config.ts
│   └── pages/
│       └── articles/
│           └── [slug].astro
├── astro.config.mjs
├── package.json
└── tsconfig.json

教程基于 astro@7.4.2 验证,日期 2026-10-24。Astro 大版本升级后内容集合 API 可能有变化,升级前请查阅对应版本的迁移指南。

搜索全站

↵