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

Коллекции контента

Добавлено в: astro@2.0.0

Коллекции контента — это лучший способ управления наборами контента в любом проекте на Astro. Коллекции помогают организовывать и запрашивать ваши документы, обеспечивают поддержку IntelliSense и проверку типов в вашем редакторе, а также предоставляют автоматическую типобезопасность TypeScript для всего вашего контента. Astro v5.0 представил API Content Layer для определения и запроса коллекций контента. Этот производительный и масштабируемый API предоставляет встроенные загрузчики контента для ваших локальных коллекций. Для удалённого контента вы можете использовать загрузчики, созданные сообществом или сторонними разработчиками, либо создать собственный загрузчик и получать данные из любого источника.

Вы можете определить коллекцию из набора данных, которые имеют схожую структуру. Это может быть каталог записей блога, JSON-файл с товарами или любые данные, представляющие несколько элементов одного типа.

Коллекции, хранящиеся локально в вашем проекте или в вашей файловой системе, могут содержать записи в формате Markdown, MDX, Markdoc, YAML или JSON:

  • Директорияsrc/
  • Директорияnewsletter/ коллекция «newsletter»
    • week-1.md запись коллекции
    • week-2.md запись коллекции
    • week-3.md запись коллекции
  • Директорияauthors/ коллекция «author»
    • authors.json один файл, содержащий все записи коллекции

С помощью подходящего загрузчика коллекций вы можете получать удалённые данные из любого внешнего источника, например, CMS, базы данных или платёжной системы без привязки к интерфейсу.

Коллекции контента полагаются на TypeScript для предоставления Zod-валидации, IntelliSense и проверки типов в вашем редакторе. Если вы не используете одну из strict или strictest настроек TypeScript в Astro, вам нужно убедиться, что в вашем tsconfig.json установлены следующие параметры compilerOptions:

tsconfig.json
{
// Включено в "astro/tsconfigs/strict" или "astro/tsconfigs/strictest"
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"strictNullChecks": true, // добавьте, если используете шаблон `base`
"allowJs": true // обязательно, включено во всех шаблонах Astro
}
}

Отдельные коллекции используют defineCollection() для настройки:

  • loader для источника данных (обязательно)
  • schema для обеспечения типобезопасности (необязательно, но настоятельно рекомендуется!)

Чтобы определить коллекции, необходимо создать файл src/content.config.ts в вашем проекте (также поддерживаются расширения .js и .mjs). Это специальный файл, который Astro будет использовать для настройки ваших коллекций контента на основе следующей структуры:

src/content.config.ts
// 1. Импортируйте утилиты из `astro:content`
import { defineCollection, z } from 'astro:content';
// 2. Импортируйте загрузчик(и)
import { glob, file } from 'astro/loaders';
// 3. Определите свои коллекции
const blog = defineCollection({ /* ... */ });
const dogs = defineCollection({ /* ... */ });
// 4. Экспортируйте объект `collections` для регистрации ваших коллекций
export const collections = { blog, dogs };

API Content Layer позволяет вам получать ваш контент (будь то локально в вашем проекте или удалённо) и использует свойство loader для извлечения ваших данных.

Astro предоставляет две встроенные функции загрузки (EN) (glob() и file()) для получения локального контента, а также доступ к API для создания собственного загрузчика и получения удалённых данных.

Загрузчик glob() (EN) создает записи из каталогов файлов Markdown, MDX, Markdoc, JSON или YAML, находящихся в любом месте файловой системы. Он принимает pattern для сопоставления файлов с использованием шаблонов glob, поддерживаемых micromatch, и базовый путь к файлам. id каждой записи автоматически генерируется из имени файла. Используйте этот загрузчик, если у вас один файл на запись.

Загрузчик file() (EN) создает несколько записей из одного локального файла. Каждая запись в файле должна иметь уникальное свойство id. Он принимает базовый путь к файлу и, при необходимости, функцию parser для файлов данных, которые не могут быть автоматически обработаны. Используйте этот загрузчик, если ваш файл данных может быть обработан как массив объектов.

src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob, file } from 'astro/loaders'; // Недоступно с устаревшим 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` может принимать как массив из нескольких шаблонов, так и строковые шаблоны
// Загружаем все markdown-файлы в каталоге space-probes, за исключением тех, которые начинаются с «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 };

Загрузчик file() принимает второй аргумент, который определяет функцию parser. Это позволяет вам указать пользовательский парсер (например, toml.parse или csv-parse) для создания коллекции из содержимого файла.

Загрузчик file() автоматически определяет и парсит массив объектов из файлов JSON и YAML (на основе их расширения) без необходимости использования parser, если у вас нет вложенного JSON-документа. Для работы с другими файлами, такими как .toml и .csv, вам нужно будет создать функцию парсера.

Следующий пример определяет коллекцию контента dogs с использованием файла .toml:

src/data/dogs.toml
[[dogs]]
id = "..."
age = "..."
[[dogs]]
id = "..."
age = "..."

После импорта парсера TOML вы можете загрузить коллекцию dogs в ваш проект, передав путь к файлу и функцию parser в загрузчик file(). Аналогичный процесс можно использовать для определения коллекции cats из файла .csv:

src/content.config.ts
import { defineCollection } from "astro:content";
import { file } from "astro/loaders";
import { parse as parseToml } from "toml";
import { parse as parseCsv } from "csv-parse/sync";
const dogs = defineCollection({
loader: file("src/data/dogs.toml", { parser: (text) => parseToml(text).dogs }),
schema: /* ... */
})
const cats = defineCollection({
loader: file("src/data/cats.csv", { parser: (text) => parseCsv(text, { columns: true, skipEmptyLines: true })})
});

Аргумент parser также позволяет загрузить одну коллекцию из вложенного JSON-документа. Например, этот JSON-файл содержит несколько коллекций:

src/data/pets.json
{"dogs": [{}], "cats": [{}]}

Вы можете разделить эти коллекции, передав пользовательский parser в загрузчик file() для каждой коллекции:

src/content.config.ts
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()), для запросов и отображения данных, а также валидацию схемы.

Вы можете определить загрузчик встроенным, внутри вашей коллекции, как асинхронную функцию, которая возвращает массив записей.

Это полезно для загрузчиков, которым не нужно вручную контролировать, как данные загружаются и хранятся. Каждый раз, когда загрузчик вызывается, он очищает хранилище и перезагружает все записи.

src/content.config.ts
const countries = defineCollection({
loader: async () => {
const response = await fetch("https://restcountries.com/v3.1/all");
const data = await response.json();
// Должен возвращать массив записей со свойством `id` или объект, где ключи — это ID, а значения — записи
return data.map((country) => ({
id: country.cca3,
...country,
}));
},
schema: /* ... */
});

Возвращенные записи сохраняются в коллекции и могут быть запрошены с помощью функций getCollection() и getEntry().

Для большего контроля над процессом загрузки вы можете использовать API Content Loader для создания объекта загрузчика. Например, имея доступ к методу load, вы можете создать загрузчик, который позволяет обновлять записи постепенно или очищает хранилище только при необходимости.

Подобно созданию интеграции Astro или плагина Vite, вы можете распространять ваш загрузчик как NPM-пакет, который другие смогут использовать в своих проектах.

Ознакомьтесь с полным API Content Loader (EN) и примерами создания собственного загрузчика.

Схемы обеспечивают согласованность метаданных или записей в коллекции через валидацию Zod. Схема гарантирует, что эти данные существуют в предсказуемой форме, когда вам нужно ссылаться на них или запрашивать их. Если какой-либо файл нарушает схему коллекции, Astro предоставит полезное сообщение об ошибке.

Схемы также обеспечивают автоматическую генерацию TypeScript-типов для вашего контента. Когда вы определяете схему для своей коллекции, Astro автоматически создает и применяет TypeScript-интерфейс к ней. В результате вы получаете полную поддержку TypeScript при запросе коллекции, включая автодополнение свойств и проверку типов.

Каждое свойство метаданных или данных ваших записей коллекции должно быть определено с использованием типа данных Zod:

src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob, file } from 'astro/loaders'; // Недоступно с устаревшим 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-типы при запросе контента внутри вашего проекта.

Чтобы использовать Zod в Astro, импортируйте утилиту z из "astro:content". Это реэкспорт библиотеки Zod, и она поддерживает все функции Zod.

// Пример: Таблица с описанием многих распространённых типов данных Zod
import { 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(),
// В разметке YAML даты, записанные без кавычек, интерпретируются как объекты Date
publishDate: z.date(),
// Преобразование строки с датой (например, "2022-07-08") в объект Date
updatedDate: z.string().transform((str) => new Date(str)),
authorContact: z.string().email(),
canonicalURL: z.string().url(),
})
})
Подробную документацию о работе Zod и доступных функциях можно найти в README Zod.

Все методы схем Zod (например, .parse(), .transform()) доступны, но с некоторыми ограничениями. В частности, выполнение пользовательских проверок валидации для изображений с использованием image().refine() не поддерживается.

Элементы коллекции также могут «ссылаться» на другие связанные элементы.

С помощью функции reference() (EN) из Collections API вы можете определить свойство в схеме коллекции как запись из другой коллекции. Например, вы можете потребовать, чтобы каждая запись space-shuttle включала свойство pilot, которое использует собственную схему коллекции pilot для проверки типа, автозаполнения и валидации.

Частым примером является запись в блоге, которая ссылается на многократно используемые профили авторов, хранящиеся в виде JSON, или URL-адреса связанных записей, хранящиеся в той же коллекции:

src/content.config.ts
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(),
// Ссылка на одного автора из коллекции `authors` по `id`
author: reference('authors'),
// Ссылка на массив связанных постов из коллекции `blog` по `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 автора записи:

src/data/blog/welcome.md
---
title: "Приветствую в моем блоге"
author: ben-holmes # ссылка на `src/data/authors/ben-holmes.json`
relatedPosts:
- about-me # ссылка на `src/data/blog/about-me.md`
- my-year-in-review # ссылка на `src/data/blog/my-year-in-review.md`
---

Эти ссылки будут преобразованы в объекты, содержащие ключ collection и ключ id, что позволит вам легко запрашивать их в ваших шаблонах.

Определение пользовательских идентификаторов

Заголовок раздела Определение пользовательских идентификаторов

При использовании загрузчика glob() с файлами Markdown, MDX, Markdoc или JSON, каждый id (EN) записи контента автоматически генерируется в формате, удобном для URL, на основе имени файла. id используется для прямого запроса записи из вашей коллекции. Это также полезно при создании новых страниц и URL-адресов из вашего контента.

Вы можете переопределить сгенерированный id записи, добавив собственное свойство slug в метаданных файла или объект данных для JSON-файлов. Это похоже на функцию «permalink» в других веб-фреймворках.

---
title: Мой пост в блоге
slug: my-custom-slug/supports/slashes
---
Здесь контент вашего поста в блоге.
src/categories/1.json
{
"title": "Моя категория",
"slug": "my-custom-id/supports/slashes",
"description": "Здесь описание вашей категории."
}

Astro предоставляет вспомогательные функции для запроса коллекции и возврата одной (или нескольких) записей контента.

  • getCollection() (EN) извлекает всю коллекцию и возвращает массив записей.
  • getEntry() (EN) извлекает одну запись из коллекции.

Эти функции возвращают записи с уникальным id, объектом data, содержащим все определённые свойства, а также body, содержащий необработанный, несобранный текст документа Markdown, MDX или Markdoc.

import { getCollection, getEntry } from 'astro:content';
// Получение всех записей из коллекции.
// Требуется имя коллекции в качестве аргумента.
const allBlogPosts = await getCollection('blog');
// Получение одной записи из коллекции.
// Требуется имя коллекции и `id`
const poodleData = await getEntry('dogs', 'poodle');
Ознакомьтесь с полным списком свойств, возвращаемых типом CollectionEntry (EN).

Использование содержимого в шаблонах Astro

Заголовок раздела Использование содержимого в шаблонах Astro

После того как вы запросили записи коллекции, вы можете получить доступ к каждой записи непосредственно в шаблоне компонента Astro. Например, вы можете создать список ссылок на записи вашего блога, отображая информацию из метаданных вашей записи с использованием свойства data.

src/pages/index.astro
---
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
---
<h1>Мои посты</h1>
<ul>
{posts.map(post => (
<li><a href={`/blog/${post.id}`}>{post.data.title}</a></li>
))}
</ul>

После запроса вы можете преобразовать записи Markdown и MDX в HTML с использованием свойства функции render() (EN). Вызов этой функции дает вам доступ к отрендеренному HTML-контенту, включая компонент <Content /> и список всех отрендеренных заголовков.

src/pages/blog/post-1.astro
---
import { getEntry, render } from 'astro:content';
const entry = await getEntry('blog', 'post-1');
if (!entry) {
// Обработка ошибки, например:
throw new Error('Не удалось найти первую запись блога');
}
const { Content, headings } = await render(entry);
---
<p>Published on: {entry.data.published.toDateString()}</p>
<Content />

Передача содержимого в качестве свойства

Заголовок раздела Передача содержимого в качестве свойства

Компонент также может передавать в качестве свойства целую запись содержимого.

В этом случае вы можете использовать утилиту CollectionEntry (EN) для корректного задания свойств компонента с помощью TypeScript. Эта утилита принимает строковый аргумент, соответствующий имени схемы вашей коллекции, и наследует все свойства схемы этой коллекции.

src/components/BlogCard.astro
---
import type { CollectionEntry } from 'astro:content';
interface Props {
post: CollectionEntry<'blog'>;
}
// `post` will match your 'blog' collection schema type
const { post } = Astro.props;
---

getCollection() принимает необязательный коллбэк «filter», который позволяет фильтровать запрос на основе свойств id или data элемента.

Вы можете использовать это для фильтрации по любым критериям содержимого, которые вам нравятся. Например, вы можете фильтровать по таким свойствам, как draft, чтобы предотвратить публикацию черновиков в блоге:

// Пример: фильтруем записи с `draft: true`
import { getCollection } from 'astro:content';
const publishedBlogEntries = await getCollection('blog', ({ data }) => {
return data.draft !== true;
});

Вы также можете создавать черновики, которые будут доступны при работе с dev-сервером, но не будут созданы в продакшене:

// Пример: фильтруем записи с `draft: true` только при сборке для продакшена
import { getCollection } from 'astro:content';
const blogEntries = await getCollection('blog', ({ data }) => {
return import.meta.env.PROD ? data.draft !== true : true;
});

Аргумент filter также поддерживает фильтрацию по вложенным каталогам внутри коллекции. Поскольку id включает в себя полный путь вложенного каталога, вы можете фильтровать по началу каждого id, чтобы возвращать элементы только из определённого вложенного каталога:

// Пример: фильтруем записи по подкаталогу в коллекции
import { getCollection } from 'astro:content';
const englishDocsEntries = await getCollection('docs', ({ id }) => {
return id.startsWith('en/');
});

Любые ссылки, определённые в вашей схеме, должны быть запрошены отдельно после первоначального запроса записи коллекции. Поскольку функция reference() (EN) преобразует ссылку в объект с ключами collection и id, вы можете использовать функцию getEntry() для возврата одного связанного элемента или getEntries() для получения нескольких связанных записей из возвращённого объекта data.

src/pages/blog/welcome.astro
---
import { getEntry, getEntries } from 'astro:content';
const blogPost = await getEntry('blog', 'welcome');
// Обрабатываем одиночную ссылку (например: `{collection: "authors", id: "ben-holmes"}`)
const author = await getEntry(blogPost.data.author);
// Обрабатываем массив ссылок
// (например: `[{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.data.name}</p>
<!-- ... -->
<h2>Также вам может понравиться:</h2>
{relatedPosts.map(post => (
<a href={post.id}>{post.data.title}</a>
))}

Коллекции контента хранятся вне каталога src/pages/. Это означает, что по умолчанию для элементов коллекции не генерируются маршруты.

Вам нужно вручную создать новый динамический маршрут, чтобы генерировать HTML-страницы из записей вашей коллекции. Ваш динамический маршрут будет отображать входящий параметр запроса (например, Astro.params.slug в src/pages/blog/[...slug].astro) для получения нужной записи в коллекции.

Точный метод генерации маршрутов будет зависеть от того, являются ли ваши страницы предварительно отрендеренными (по умолчанию) или рендерятся по запросу на сервере.

Сборка для статического вывода (по умолчанию)

Заголовок раздела Сборка для статического вывода (по умолчанию)

Если вы создаете статический веб-сайт (поведение Astro по умолчанию), вы можете использовать функцию getStaticPaths() (EN) для создания нескольких страниц из одного компонента src/pages/ во время сборки.

Вызовите getCollection() внутри getStaticPaths(), чтобы данные вашей коллекции были доступны для построения статических маршрутов. Затем создайте отдельные URL-пути, используя свойство id каждой записи контента. Каждая страница получает всю запись коллекции в качестве пропса для использования в вашем шаблоне страницы.

src/pages/posts/[id].astro
---
import { getCollection, render } from 'astro:content';
// 1. Создайте новый путь для каждого элемента коллекции
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { id: post.id },
props: { post },
}));
}
// 2. Для вашего шаблона вы можете получить запись непосредственно из свойства
const { post } = Astro.props;
const { Content } = await render(post);
---
<h1>{post.data.title}</h1>
<Content />

Это создаст новую страницу для каждой записи в коллекции blog. Например, запись в src/blog/hello-world.md будет иметь id hello-world, и поэтому её конечный URL будет /posts/hello-world/.

Если вы создаете динамический веб-сайт (используя поддержку SSR в Astro), не стоит заранее генерировать пути во время сборки. Вместо этого ваша страница должна исследовать запрос (с помощью Astro.request или Astro.params), чтобы найти slug по требованию, а затем получить его с помощью [getEntry()`](/ru/reference/modules/astro-content/#getentry).

src/pages/posts/[id].astro
---
import { getEntry, render } from "astro:content";
// 1. Получение id из входящего запроса сервера
const { id } = Astro.params;
if (id === undefined) {
return Astro.redirect("/404");
}
// 2. Запросите запись напрямую, используя id из запроса
const post = await getEntry("blog", id);
// 3. Перенаправление, если запись не существует
if (post === undefined) {
return Astro.redirect("/404");
}
// 4. (Необязательно) Верните запись в HTML в шаблоне
const { Content } = await render(post);
---
<h1>{post.data.title}</h1>
<Content />

Вы можете создать коллекцию в любой момент, когда у вас есть группа связанных данных или контента, имеющего общую структуру.

Большая часть преимуществ использования коллекций заключается в следующем:

  • Определение общей структуры данных для проверки того, что отдельная запись является «правильной» или «полной», что позволяет избежать ошибок в продакшене.
  • API, ориентированные на контент, разработанные для интуитивного запроса (например, getCollection() вместо import.meta.glob()), при импорте и рендеринге контента на ваших страницах.
  • Content Loader API (EN) для получения вашего контента, который предоставляет как встроенные загрузчики, так и доступ к низкоуровневому API. Доступно несколько загрузчиков, созданных сообществом и сторонними разработчиками, и вы можете создать собственный загрузчик для получения данных из любого источника.
  • Производительность и масштабируемость. API Content Layer позволяет кэшировать данные между сборками и подходит для десятков тысяч записей контента.

Определяйте ваши данные как коллекцию, когда:

  • У вас есть несколько файлов или данных для организации, которые имеют одинаковую общую структуру (например, записи блога, написанные в Markdown, которые имеют одинаковые свойства метаданных).
  • У вас есть существующий контент, хранящийся удалённо, например, в CMS, и вы хотите воспользоваться преимуществами вспомогательных функций коллекций и API Content Layer вместо использования fetch() или SDK.
  • Вам нужно получить (десятки) тысяч связанных данных, и требуется метод запроса и кэширования, который работает в масштабе.

Коллекции обеспечивают отличную структуру, безопасность и организацию, когда у вас есть несколько элементов контента, которые должны иметь одинаковые свойства.

Коллекции могут не подойти, если:

  • У вас есть только одна или небольшое количество разных страниц. Вместо этого рассмотрите возможность создания отдельных компонентов страниц, таких как src/pages/about.astro, с вашим контентом напрямую.
  • Вы отображаете файлы, которые не обрабатываются Astro, например PDF. Разместите эти статические ресурсы в каталоге public/ вашего проекта.
  • Ваш источник данных имеет собственный SDK/клиентскую библиотеку для импорта, которая несовместима с загрузчиком контента или не предоставляет его, и вы предпочитаете использовать её напрямую.
  • Вы используете API, которые должны обновляться в реальном времени. Коллекции контента обновляются только во время сборки, поэтому, если вам нужны актуальные данные, используйте другие методы импорта файлов (EN) или получения данных с рендерингом по запросу (EN).
Внести свой вклад Сообщество Sponsor