Перейти к содержимому

14. Content Collections

Content Collections — это встроенная система Astro для управления контентом с типобезопасностью. Вместо хаоса папок с Markdown-файлами ты получаешь структурированные, валидированные коллекции с автодополнением TypeScript.

Думай о Content Collections как о легковесной базе данных прямо в файловой системе: каждая папка в src/content/ — это таблица, каждый файл — запись, а Zod-схема — это определение колонок.


Все коллекции живут в папке src/content/:

src/
content/
config.ts ← схемы всех коллекций
blog/
first-post.md
second-post.mdx
authors/
alex.json
maria.yaml
tags/
astro.json
javascript.json

src/content/config.ts
import { defineCollection, z, reference } from 'astro:content';
const authorsCollection = defineCollection({
type: 'data',
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string().url(),
social: z.object({
twitter: z.string().optional(),
github: z.string().optional(),
}),
}),
});
const tagsCollection = defineCollection({
type: 'data',
schema: z.object({
label: z.string(),
color: z.string(),
}),
});
const blogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
publishDate: z.date(),
author: reference('authors'), // ← ссылка на другую коллекцию!
tags: z.array(reference('tags')),
draft: z.boolean().default(false),
image: z.string().optional(),
}),
});
export const collections = {
blog: blogCollection,
authors: authorsCollection,
tags: tagsCollection,
};

Zod — это библиотека валидации, которую Astro использует для проверки данных в коллекциях:

z.string() // строка
z.number() // число
z.boolean() // булево
z.date() // дата (из строки ISO 8601)
z.array(z.string()) // массив строк
z.enum(['a', 'b']) // одно из значений
z.optional() // необязательное поле
z.default(false) // значение по умолчанию
reference('authors') // ссылка на запись в другой коллекции

Если фронтматер файла не соответствует схеме, Astro выдаёт ошибку при сборке. Никаких undefined is not a function в рантайме!


---
import { getCollection, getEntry } from 'astro:content';
// Все публикации (кроме черновиков)
const posts = await getCollection('blog', ({ data }) => !data.draft);
// Сортировка по дате
const sorted = posts.sort((a, b) =>
b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
);
// Конкретная запись
const author = await getEntry('authors', 'alex');
// Запись через reference-поле
const firstPost = posts[0];
const postAuthor = await getEntry(firstPost.data.author);
---

src/pages/blog/[slug].astro
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>