TS
#astro#typescript#content-collections#filter#sort#blog

Astro: Content Collections — фильтр по тегу, сортировка по дате

Работа с Content Collections: получение коллекции, фильтрация по тегам и сортировка по дате публикации.

Как использовать

  1. Импортируйте getCollection из astro:content и вызовите с именем коллекции (например blog) и функцией-фильтром по data.
  2. Примените .sort() по data.pubDate.valueOf() для сортировки по дате (новые первыми — b - a).
  3. Используйте в frontmatter страницы или компонента; для пагинации добавьте .slice(offset, offset + limit).

В Astro списки постов и статей обычно хранят в Content Collections. Чтобы вывести только записи по тегу или категории и в нужном порядке (например новые первыми), коллекцию нужно фильтровать при получении и сортировать по дате. Проблема возникает, когда вызывают getCollection('blog') без фильтра и потом фильтруют вручную — лишняя работа и риск забыть исключить черновики. Симптомы: на странице отображаются черновики (draft), порядок по дате не тот или постов больше, чем нужно. Ниже — примеры фильтрации по одному тегу, по нескольким (AND/OR), по категории, сортировка по pubDate и получение последних N записей; плюс проверка в dev-режиме.

Решение

Получение коллекции контента с фильтрацией по тегам и сортировкой по дате. Используйте для вывода списков постов, фильтрации по категориям.

import { getCollection } from 'astro:content';

// 1. Получить все посты с фильтром по тегу
const postsWithTag = (await getCollection('blog', ({ data }) => {
  return !data.draft && data.tags?.includes('astro');
}))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

// 2. Получить посты по нескольким тегам (AND)
const postsWithMultipleTags = (await getCollection('blog', ({ data }) => {
  return !data.draft 
    && data.tags?.includes('astro')
    && data.tags?.includes('tutorial');
}))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

// 3. Получить посты по любому из тегов (OR)
const targetTags = ['astro', 'react', 'vue'];
const postsWithAnyTag = (await getCollection('blog', ({ data }) => {
  return !data.draft 
    && data.tags?.some(tag => targetTags.includes(tag));
}))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

// 4. Получить последние N постов
const latestPosts = (await getCollection('blog', ({ data }) => !data.draft))
  .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
  .slice(0, 5);

// 5. Получить посты по категории и отсортировать
const categoryPosts = (await getCollection('blog', ({ data }) => {
  return !data.draft && data.category === 'Frontend';
}))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());

// 6. Получить уникальные теги из всех постов
const allPosts = await getCollection('blog', ({ data }) => !data.draft);
const allTags = Array.from(
  new Set(allPosts.flatMap(post => post.data.tags || []))
).sort();

Использование в компоненте:

---
// src/components/PostList.astro
import { getCollection } from 'astro:content';

const posts = (await getCollection('blog', ({ data }) => 
  !data.draft && data.tags?.includes('tutorial')
))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---

<ul>
  {posts.map(post => (
    <li>
      <a href={`/blog/${post.slug}/`}>{post.data.title}</a>
      <time>{post.data.pubDate.toLocaleDateString('ru')}</time>
    </li>
  ))}
</ul>

Фильтр в getCollection() применяется на этапе получения коллекции, что эффективнее фильтрации после. getCollection() возвращает промис — не забывайте await. Сортировка по pubDate.valueOf() корректна только если в схеме коллекции pubDate — тип Date (проверьте src/content/config.ts).

Проверка

  1. Запуск dev и проверка страницы со списком — убедитесь, что отображаются только посты с нужным тегом и в порядке убывания даты:
pnpm dev

Откройте страницу, которая использует фильтр (например /blog/ или страницу тега). Черновики (draft: true) не должны попадать в список; порядок — сначала самые новые.

  1. Диагностика: сколько записей вернула коллекция — во frontmatter временно выведите длину массива:
---
const posts = (await getCollection('blog', ({ data }) => !data.draft))
  .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
console.log('Posts count:', posts.length);
---

Сверьте с ожидаемым количеством постов в src/content/blog/.

  1. Проверка схемы коллекции — в src/content/config.ts у коллекции blog поле pubDate должно быть с типом z.date() или аналогом, иначе .valueOf() может быть недоступен или дата окажется строкой.

Типичные ошибки

  • Черновики попадают в список — в фильтр не добавлено условие !data.draft. В callback getCollection('blog', ({ data }) => ...) обязательно включайте проверку на draft, если в коллекции есть черновики.
  • Сортировка не срабатывает или порядок неверный — убедитесь, что pubDate в схеме — Date. Если в frontmatter строка, приведите к дате в схеме (например z.coerce.date()). Для «сначала новые» используйте b.data.pubDate.valueOf() - a.data.pubDate.valueOf().
  • getCollection не найден или коллекция пустая — проверьте имя коллекции (должно совпадать с папкой в src/content/) и что в config.ts коллекция зарегистрирована. Импорт: import { getCollection } from 'astro:content'.

Где применять

  • Статические страницы блога: главная с последними постами, страницы по тегам/категориям, виджет «последние N записей». Сборка — на этапе build, данные берутся из Content Collections.
  • Dev и prod: один и тот же код; в prod после pnpm build список фиксируется в HTML.

Связанные сниппеты: Статический вывод по умолчанию, Статическая страница с минимальным HTML, API Route (endpoint).