内容集合
新增于: astro@2.0.0
内容集合是在任何 Astro 项目中管理内容集的最佳方式。集合有助于组织和查询你的文档,在编辑器中启用智能提示和类型检查,并为你的所有内容提供自动的 TypeScript 类型安全。Astro v5.0 引入了内容层 API,用于定义和查询内容集合。这个高性能、可扩展的 API 为你的本地集合提供了内置的内容加载器。对于远程内容,你可以使用第三方和社区构建的加载器,或者创建自己的自定义加载器,从任何来源拉取数据。
项目可以继续使用 Astro v2.0 中引入的旧版内容集合 API。但是,我们鼓励你在有能力的时候更新任何现有的集合。
什么是内容集合?
名为“什么是内容集合?”的部分你可以从一组结构相似的数据中定义一个**集合**。这可以是一个博客文章的目录、一个产品项的 JSON 文件,或任何代表多个相同形状项目的数据。
在项目中或文件系统上本地存储的集合可以包含 Markdown、MDX、Markdoc、YAML、TOML 或 JSON 文件的条目。
目录src/
- …
目录newsletter/ “newsletter” 集合
- week-1.md 一个集合条目
- week-2.md 一个集合条目
- week-3.md 一个集合条目
目录authors/ “author” 集合
- authors.json 一个包含所有集合条目的文件
通过适当的集合加载器,你可以从任何外部来源(如 CMS、数据库或无头支付系统)获取远程数据。
集合的 TypeScript 配置
名为“集合的 TypeScript 配置”的部分内容集合依赖 TypeScript 来提供 Zod 验证、编辑器中的智能提示和类型检查。如果你没有扩展 Astro 的 strict
或 strictest
TypeScript 设置之一,你需要确保在你的 tsconfig.json
中设置了以下 compilerOptions
{ // Included with "astro/tsconfigs/strict" or "astro/tsconfigs/strictest" "extends": "astro/tsconfigs/base", "compilerOptions": { "strictNullChecks": true, // add if using `base` template "allowJs": true // required, and included with all Astro templates }}
定义集合
名为“定义集合”的部分单个集合使用 defineCollection()
来配置
- 数据源的
loader
(必需) - 类型安全的
schema
(可选,但强烈推荐!)
集合配置文件
名为“集合配置文件”的部分要定义集合,你必须在项目中创建一个 src/content.config.ts
文件(也支持 .js
和 .mjs
扩展名)。这是一个特殊文件,Astro 将使用它根据以下结构配置你的内容集合
// 1. Import utilities from `astro:content`import { defineCollection, z } from 'astro:content';
// 2. Import loader(s)import { glob, file } from 'astro/loaders';
// 3. Define your collection(s)const blog = defineCollection({ /* ... */ });const dogs = defineCollection({ /* ... */ });
// 4. Export a single `collections` object to register your collection(s)export const collections = { blog, dogs };
定义集合 loader
名为“定义集合加载器”的部分内容层 API 允许你获取内容(无论是本地存储在项目中还是远程存储),并使用 loader
属性来检索数据。
内置加载器
名为“内置加载器”的部分Astro 为获取本地内容提供了两个内置加载器函数(glob()
和 file()
),同时还提供了 API 访问权限,以构建你自己的加载器并获取远程数据。
glob()
加载器从文件系统中的任何位置的 Markdown、MDX、Markdoc、JSON、YAML 或 TOML 文件的目录中创建条目。它接受一个用于匹配条目文件的 pattern
,该模式使用 micromatch 支持的 glob 模式,以及文件所在的基本文件路径。每个条目的 id
将根据其文件名自动生成。当你每个条目对应一个文件时,请使用此加载器。
file()
加载器从单个本地文件创建多个条目。文件中的每个条目都必须有一个唯一的 id
键属性。它接受一个指向你文件的 base
文件路径,并可选地接受一个parser
函数,用于它无法自动解析的数据文件。当你的数据文件可以被解析为对象数组时,请使用此加载器。
import { defineCollection, z } from 'astro:content';import { glob, file } from 'astro/loaders'; // Not available with legacy API
const blog = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }), schema: /* ... */});const dogs = defineCollection({ loader: file("src/data/dogs.json"), schema: /* ... */});
const probes = defineCollection({ // `loader` can accept an array of multiple patterns as well as string patterns // Load all markdown files in the space-probes directory, except for those that start with "voyager-" loader: glob({ pattern: ['*.md', '!voyager-*'], base: 'src/data/space-probes' }), schema: z.object({ name: z.string(), type: z.enum(['Space Probe', 'Mars Rover', 'Comet Lander']), launch_date: z.date(), status: z.enum(['Active', 'Inactive', 'Decommissioned']), destination: z.string(), operator: z.string(), notable_discoveries: z.array(z.string()), }),});
export const collections = { blog, dogs, probes };
parser
函数
名为“parser 函数”的部分file()
加载器接受第二个参数,该参数定义一个 parser
函数。这允许你指定一个自定义解析器(例如 csv-parse
)来从文件内容创建一个集合。
file()
加载器将自动检测并解析(根据文件扩展名)来自 JSON 和 YAML 文件的单个对象数组,并将 TOML 文件中的每个顶级表视为一个独立的条目。对这些文件类型的支持是内置的,除非你有嵌套的 JSON 文档,否则不需要 parser
。要使用其他文件,如 .csv
,你需要创建一个解析器函数。
以下示例展示了如何导入一个 CSV 解析器,然后通过向 file()
加载器传递文件路径和 parser
函数,将一个 cats
集合加载到你的项目中
import { defineCollection } from "astro:content";import { file } from "astro/loaders";import { parse as parseCsv } from "csv-parse/sync";
const cats = defineCollection({ loader: file("src/data/cats.csv", { parser: (text) => parseCsv(text, { columns: true, skipEmptyLines: true })})});
嵌套的 .json
文档
名为“嵌套的 .json 文档”的部分parser
参数还允许你从嵌套的 JSON 文档中加载单个集合。例如,这个 JSON 文件包含多个集合
{"dogs": [{}], "cats": [{}]}
你可以通过为每个集合向 file()
加载器传递一个自定义 parser
来分离这些集合
const dogs = defineCollection({ loader: file("src/data/pets.json", { parser: (text) => JSON.parse(text).dogs })});const cats = defineCollection({ loader: file("src/data/pets.json", { parser: (text) => JSON.parse(text).cats })});
构建自定义加载器
名为“构建自定义加载器”的部分你可以构建一个自定义加载器,从任何数据源(如 CMS、数据库或 API 端点)获取远程内容。
使用加载器获取数据将自动从你的远程数据创建一个集合。这为你带来了本地集合的所有好处,例如集合特定的 API 辅助函数 getCollection()
和 render()
来查询和显示数据,以及模式验证。
在 Astro 集成目录中查找社区构建和第三方的加载器。
内联加载器
名为“内联加载器”的部分你可以在集合内部将加载器以内联方式定义为一个异步函数,该函数返回一个条目数组。
这对于不需要手动控制数据加载和存储方式的加载器很有用。每当加载器被调用时,它都会清除存储并重新加载所有条目。
const countries = defineCollection({ loader: async () => { const response = await fetch("https://restcountries.com/v3.1/all"); const data = await response.json(); // Must return an array of entries with an id property, or an object with IDs as keys and entries as values return data.map((country) => ({ id: country.cca3, ...country, })); }, schema: /* ... */});
返回的条目存储在集合中,可以使用 getCollection()
和 getEntry()
函数进行查询。
加载器对象
名为“加载器对象”的部分为了更好地控制加载过程,你可以使用内容加载器 API 创建一个加载器对象。例如,通过直接访问 load
方法,你可以创建一个允许增量更新条目或仅在必要时清除存储的加载器。
与创建 Astro 集成或 Vite 插件类似,你可以将你的加载器作为 NPM 包分发,供他人在其项目中使用。
定义集合模式
名为“定义集合模式”的部分模式通过 Zod 验证强制集合中的 frontmatter 或条目数据保持一致。模式**保证**当您需要引用或查询数据时,这些数据以可预测的形式存在。如果任何文件违反了其集合模式,Astro 将提供一个有用的错误来通知您。
模式还为您的内容提供了 Astro 的自动 TypeScript 类型定义。当您为集合定义模式时,Astro 会自动为其生成并应用 TypeScript 接口。结果是当您查询集合时获得完整的 TypeScript 支持,包括属性自动补全和类型检查。
集合条目的每个 frontmatter 或数据属性都必须使用 Zod 数据类型来定义
import { defineCollection, z } from 'astro:content';import { glob, file } from 'astro/loaders'; // Not available with legacy API
const blog = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }), schema: z.object({ title: z.string(), description: z.string(), pubDate: z.coerce.date(), updatedDate: z.coerce.date().optional(), })});const dogs = defineCollection({ loader: file("src/data/dogs.json"), schema: z.object({ id: z.string(), breed: z.string(), temperament: z.array(z.string()), }),});
export const collections = { blog, dogs };
使用 Zod 定义数据类型
名为“使用 Zod 定义数据类型”的部分Astro 使用 Zod 来支持其内容模式。通过 Zod,Astro 能够验证集合中每个文件的数据,并在您从项目中查询内容时提供自动的 TypeScript 类型。
要在 Astro 中使用 Zod,请从 "astro:content"
导入 z
实用工具。这是 Zod 库的重新导出,支持 Zod 的所有功能。
// Example: A cheatsheet of many common Zod datatypesimport { z, defineCollection } from 'astro:content';
defineCollection({ schema: z.object({ isDraft: z.boolean(), title: z.string(), sortOrder: z.number(), image: z.object({ src: z.string(), alt: z.string(), }), author: z.string().default('Anonymous'), language: z.enum(['en', 'es']), tags: z.array(z.string()), footnote: z.string().optional(),
// In YAML, dates written without quotes around them are interpreted as Date objects publishDate: z.date(), // e.g. 2024-09-17
// Transform a date string (e.g. "2022-07-08") to a Date object updatedDate: z.string().transform((str) => new Date(str)),
authorContact: z.string().email(), canonicalURL: z.string().url(), })})
Zod 模式方法
名为“Zod 模式方法”的部分所有 Zod 模式方法(例如 .parse()
、.transform()
)都可用,但有一些限制。值得注意的是,不支持使用 image().refine()
对图像执行自定义验证检查。
定义集合引用
名为“定义集合引用”的部分集合条目也可以“引用”其他相关条目。
使用集合 API 中的 reference()
函数,您可以在集合模式中将一个属性定义为另一个集合的条目。例如,您可以要求每个 space-shuttle
条目都包含一个 pilot
属性,该属性使用 pilot
集合自己的模式进行类型检查、自动补全和验证。
一个常见的例子是,一篇博客文章引用存储为 JSON 的可复用作者资料,或引用存储在同一集合中的相关文章 URL。
import { defineCollection, reference, z } from 'astro:content';import { glob } from 'astro/loaders';
const blog = defineCollection({ loader: glob({ pattern: '**/[^_]*.md', base: "./src/data/blog" }), schema: z.object({ title: z.string(), // Reference a single author from the `authors` collection by `id` author: reference('authors'), // Reference an array of related posts from the `blog` collection by `slug` relatedPosts: z.array(reference('blog')), })});
const authors = defineCollection({ loader: glob({ pattern: '**/[^_]*.json', base: "./src/data/authors" }), schema: z.object({ name: z.string(), portfolio: z.string().url(), })});
export const collections = { blog, authors };
这篇示例博客文章指定了相关文章的 id
和文章作者的 id
---title: "Welcome to my blog"author: ben-holmes # references `src/data/authors/ben-holmes.json`relatedPosts:- about-me # references `src/data/blog/about-me.md`- my-year-in-review # references `src/data/blog/my-year-in-review.md`---
这些引用将被转换为包含 collection
键和 id
键的对象,让您可以轻松地在您的模板中查询它们。
定义自定义 ID
名为“定义自定义 ID”的部分当使用 glob()
加载器处理 Markdown、MDX、Markdoc 或 JSON 文件时,每个内容条目的id
都会根据内容文件名自动生成为 URL 友好的格式。id
用于直接从集合中查询条目。在从内容创建新页面和 URL 时也很有用。
您可以通过在文件 frontmatter 或 JSON 文件的数据对象中添加您自己的 slug
属性来覆盖条目生成的 id
。这与其他 Web 框架的“永久链接”功能类似。
---title: My Blog Postslug: my-custom-id/supports/slashes---Your blog post content here.
{ "title": "My Category", "slug": "my-custom-id/supports/slashes", "description": "Your category description here."}
查询集合
名为“查询集合”的部分Astro 提供了辅助函数来查询集合并返回一个(或多个)内容条目。
getCollection()
获取整个集合并返回一个条目数组。getEntry()
从集合中获取单个条目。
这些函数返回的条目包含一个唯一的 id
、一个包含所有已定义属性的 data
对象,并且对于 Markdown、MDX 或 Markdoc 文档,还会返回一个包含原始、未编译正文的 body
。
import { getCollection, getEntry } from 'astro:content';
// Get all entries from a collection.// Requires the name of the collection as an argument.const allBlogPosts = await getCollection('blog');
// Get a single entry from a collection.// Requires the name of the collection and `id`const poodleData = await getEntry('dogs', 'poodle');
生成的集合的排序顺序是不确定的且依赖于平台。这意味着如果您调用 getCollection()
并需要按特定顺序返回条目(例如,按日期排序的博客文章),您必须自己对集合条目进行排序。
---import { getCollection } from 'astro:content';
const posts = (await getCollection('blog')).sort( (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),);---
CollectionEntry
类型返回的属性的完整列表。
在 Astro 模板中使用内容
名为“在 Astro 模板中使用内容”的部分查询完您的集合后,您可以直接在 Astro 组件模板中访问每个条目的内容。例如,您可以创建一个指向您博客文章的链接列表,使用 data
属性显示您条目 frontmatter 中的信息。
---import { getCollection } from 'astro:content';const posts = await getCollection('blog');---<h1>My posts</h1><ul> {posts.map(post => ( <li><a href={`/blog/${post.id}`}>{post.data.title}</a></li> ))}</ul>
渲染正文内容
名为“渲染正文内容”的部分查询后,您可以使用 render()
函数属性将 Markdown 和 MDX 条目渲染为 HTML。调用此函数可以让您访问渲染后的 HTML 内容,包括一个 <Content />
组件和所有渲染标题的列表。
---import { getEntry, render } from 'astro:content';
const entry = await getEntry('blog', 'post-1');if (!entry) { // Handle Error, for example: throw new Error('Could not find blog post 1');}const { Content, headings } = await render(entry);---<p>Published on: {entry.data.published.toDateString()}</p><Content />
将内容作为 props 传递
名为“将内容作为 props 传递”的部分组件也可以将整个集合条目作为 prop 传递。
您可以使用 CollectionEntry
实用工具来使用 TypeScript 正确地类型化组件的 props。此实用工具接受一个与您的集合模式名称匹配的字符串参数,并将继承该集合模式的所有属性。
---import type { CollectionEntry } from 'astro:content';interface Props { post: CollectionEntry<'blog'>;}
// `post` will match your 'blog' collection schema typeconst { post } = Astro.props;---
过滤集合查询
名为“过滤集合查询”的部分getCollection()
接受一个可选的“过滤器”回调函数,允许您根据条目的 id
或 data
属性来过滤查询。
您可以使用它来按任何您喜欢的内容标准进行过滤。例如,您可以按 draft
等属性进行过滤,以防止任何草稿博客文章发布到您的博客上
// Example: Filter out content entries with `draft: true`import { getCollection } from 'astro:content';const publishedBlogEntries = await getCollection('blog', ({ data }) => { return data.draft !== true;});
您还可以创建在运行开发服务器时可用,但在生产构建中不可用的草稿页面
// Example: Filter out content entries with `draft: true` only when building for productionimport { getCollection } from 'astro:content';const blogEntries = await getCollection('blog', ({ data }) => { return import.meta.env.PROD ? data.draft !== true : true;});
过滤器参数还支持按集合中的嵌套目录进行过滤。由于 id
包含完整的嵌套路径,因此您可以通过过滤每个 id
的开头来仅返回特定嵌套目录中的项目
// Example: Filter entries by sub-directory in the collectionimport { getCollection } from 'astro:content';const englishDocsEntries = await getCollection('docs', ({ id }) => { return id.startsWith('en/');});
访问引用数据
名为“访问引用数据”的部分在首次查询集合条目后,任何在您的模式中定义的引用都必须单独查询。由于 reference()
函数将引用转换为一个包含 collection
和 id
键的对象,因此您可以使用 getEntry()
函数返回单个引用的项目,或使用 getEntries()
从返回的 data
对象中检索多个引用的条目。
---import { getEntry, getEntries } from 'astro:content';
const blogPost = await getEntry('blog', 'welcome');
// Resolve a singular reference (e.g. `{collection: "authors", id: "ben-holmes"}`)const author = await getEntry(blogPost.data.author);// Resolve an array of references// (e.g. `[{collection: "blog", id: "about-me"}, {collection: "blog", id: "my-year-in-review"}]`)const relatedPosts = await getEntries(blogPost.data.relatedPosts);---
<h1>{blogPost.data.title}</h1><p>Author: {author.data.name}</p>
<!-- ... -->
<h2>You might also like:</h2>{relatedPosts.map(post => ( <a href={post.id}>{post.data.title}</a>))}
从内容生成路由
名为“从内容生成路由”的部分内容集合存储在 src/pages/
目录之外。这意味着默认情况下,不会为您的集合项生成任何页面或路由。
如果您想为每个集合条目(例如单个博客文章)生成 HTML 页面,您需要手动创建一个新的动态路由。您的动态路由将映射传入的请求参数(例如 src/pages/blog/[...slug].astro
中的 Astro.params.slug
)以获取每个页面的正确条目。
生成路由的确切方法将取决于您的页面是预渲染的(默认)还是由服务器按需渲染的。
构建静态输出(默认)
名为“构建静态输出(默认)”的部分如果您正在构建一个静态网站(Astro 的默认行为),请使用 getStaticPaths()
函数在构建期间从单个页面组件(例如 src/pages/[slug]
)创建多个页面。
在 getStaticPaths()
内部调用 getCollection()
,以便您的集合数据可用于构建静态路由。然后,使用每个内容条目的 id
属性创建各个 URL 路径。每个页面都将整个集合条目作为 prop 传递,以供在您的页面模板中使用。
---import { getCollection, render } from 'astro:content';// 1. Generate a new path for every collection entryexport async function getStaticPaths() { const posts = await getCollection('blog'); return posts.map(post => ({ params: { id: post.id }, props: { post }, }));}// 2. For your template, you can get the entry directly from the propconst { post } = Astro.props;const { Content } = await render(post);---<h1>{post.data.title}</h1><Content />
这将为 blog
集合中的每个条目生成一个页面路由。例如,位于 src/blog/hello-world.md
的条目将具有 hello-world
的 id
,因此其最终 URL 将为 /posts/hello-world/
。
如果您的自定义 slug 包含 /
字符以生成具有多个路径段的 URL,则必须在此动态路由页面的 .astro
文件名中使用rest 参数(例如 [...slug]
)。
构建服务器输出 (SSR)
名为“构建服务器输出 (SSR)”的部分如果您正在构建一个动态网站(使用 Astro 的 SSR 支持),则在构建期间不需要预先生成任何路径。相反,您的页面应该检查请求(使用 Astro.request
或 Astro.params
)以按需查找 slug
,然后使用 getEntry()
获取它。
---import { getEntry, render } from "astro:content";// 1. Get the slug from the incoming server requestconst { id } = Astro.params;if (id === undefined) { return Astro.redirect("/404");}// 2. Query for the entry directly using the request slugconst post = await getEntry("blog", id);// 3. Redirect if the entry does not existif (post === undefined) { return Astro.redirect("/404");}// 4. Render the entry to HTML in the templateconst { Content } = await render(post);---<h1>{post.data.title}</h1><Content />
浏览 GitHub 上的博客教程演示代码的 src/pages/
文件夹,查看从集合创建页面的完整示例,例如博客文章列表、标签页等博客功能!
何时创建集合
名为“何时创建集合”的部分任何时候,当你有一组共享共同结构的相关数据或内容时,你都可以创建一个集合。
使用集合的大部分好处来自于
- 定义一个通用的数据结构,以验证单个条目是否“正确”或“完整”,从而避免生产环境中的错误。
- 以内容为中心的 API,旨在使查询更直观(例如,使用
getCollection()
而不是import.meta.glob()
),以便在页面上导入和渲染内容。 - 一个内容加载器 API,用于检索您的内容,它提供了内置加载器和对低级 API 的访问。有几个第三方和社区构建的加载器可用,您也可以构建自己的自定义加载器从任何地方获取数据。
- 性能和可扩展性。内容层 API 允许数据在构建之间被缓存,适用于数以万计的内容条目。
在以下情况下将你的数据定义为集合:
- 您有多个文件或数据需要组织,它们共享相同的整体结构(例如,用 Markdown 写的博客文章,都有相同的前置元数据属性)。
- 您有现有的内容存储在远程,例如在 CMS 中,并且希望利用集合辅助函数和内容层 API,而不是使用
fetch()
或 SDK。 - 你需要获取(数以万计的)相关数据,并且需要一种能够处理大规模数据的查询和缓存方法。
何时不创建集合
名为“何时不创建集合”的部分当你拥有**多个必须共享相同属性的内容**时,集合提供了出色的结构、安全性和组织性。
在以下情况下,集合**可能不是你的解决方案**:
- 您只有一个或少数几个不同的页面。可以考虑制作单独的页面组件,例如
src/pages/about.astro
,直接在其中包含您的内容。 - 您正在显示 Astro 不处理的文件,例如 PDF。请将这些静态资源放在您项目的
public/
目录中。 - 您的数据源有自己的 SDK/客户端库用于导入,与内容加载器不兼容或不提供内容加载器,而您更喜欢直接使用它。
- 您正在使用需要实时更新的 API。内容集合仅在构建时更新,因此如果您需要实时数据,请使用其他导入文件或使用按需渲染获取数据的方法。