lenec ru

← все посты

Как добавить RSS и sitemap в Astro

16K

RSS и sitemap.xml — два маленьких файла, без которых блог в 2026 году выглядит несерьёзно. RSS читают агрегаторы (от Feedly до Inoreader) и куча уютных читалок. Sitemap нужен поисковикам, чтобы они быстрее находили страницы. В Astro оба ставятся за пять минут — но есть моменты, на которых я в своё время спотыкалась.

Sitemap через официальную интеграцию

Astro поставляет официальную интеграцию @astrojs/sitemap. Ставится одной командой:

pnpm astro add sitemap

Команда подтянет пакет и добавит интеграцию в astro.config.mjs. После этого обязательно укажи site — sitemap без полного URL не соберётся:

import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://example.com',
  integrations: [sitemap()],
});

После сборки в dist/ появятся sitemap-index.xml и sitemap-0.xml. Если страниц больше 45 000, интеграция автоматически разрежет sitemap на несколько файлов.

Что положить в robots.txt

Sitemap сам себя не пропишет в robots.txt. У меня public/robots.txt выглядит так:

User-agent: *
Allow: /

Sitemap: https://example.com/sitemap-index.xml

Без этой строчки Google и Яндекс всё равно найдут карту, но Bing и часть мелких ботов — нет.

Фильтрация и приоритеты

Sitemap по умолчанию включает все страницы. Если у тебя есть служебные (страница входа, превью статей, теги без контента) — отфильтруй:

sitemap({
  filter: (page) => !page.includes('/admin/') && !page.includes('/draft/'),
  changefreq: 'weekly',
  priority: 0.7,
  serialize: (item) => {
    if (item.url === 'https://example.com/') {
      return { ...item, changefreq: 'daily', priority: 1.0 };
    }
    return item;
  },
});

Поля priority и changefreq поисковики читают как подсказки. Не надо делать всё «1.0» — просто проигнорируют. Приоритет 1.0 — главная, 0.8 — основные разделы, 0.6–0.7 — статьи, 0.4 — служебные.

RSS: ставится отдельно

Для RSS отдельная официальная либа:

pnpm add @astrojs/rss

Дальше создаёшь маршрут src/pages/rss.xml.ts:

import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';

export async function GET(context) {
  const posts = await getCollection('blog');
  return rss({
    title: 'Заметки Полины',
    description: 'О Astro, MDX и контенте',
    site: context.site,
    items: posts
      .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf())
      .map((post) => ({
        title: post.data.title,
        pubDate: post.data.pubDate,
        description: post.data.description,
        link: `/p/${post.slug}/`,
      })),
  });
}

После билда файл будет доступен по /rss.xml. Я обычно добавляю его в <head>, чтобы браузеры сразу видели feed:

<link
  rel="alternate"
  type="application/rss+xml"
  title="Заметки Полины"
  href="/rss.xml"
/>

Полные тексты или анонсы

Самый частый вопрос — отдавать в RSS только описание или полный текст статьи. Зависит от целей. Если хочется, чтобы читатели подписывались на сайт и заходили внутрь — короткое описание со ссылкой. Если важна доступность контента где угодно — полный текст в content:

items: posts.map((post) => ({
  title: post.data.title,
  pubDate: post.data.pubDate,
  description: post.data.description,
  link: `/p/${post.slug}/`,
  content: post.body,
}));

В моих проектах работает гибрид: описание короткое, полный текст уезжает только если есть тег full-rss.

Картинки и enclosures

Если хочешь, чтобы RSS-читалки показывали обложку поста — добавь enclosure:

items: posts.map((post) => ({
  title: post.data.title,
  pubDate: post.data.pubDate,
  description: post.data.description,
  link: `/p/${post.slug}/`,
  enclosure: post.data.cover
    ? { url: post.data.cover, length: 0, type: 'image/webp' }
    : undefined,
}));

Длину можно проставить нулём — большинство читалок её игнорируют.

Локализация

Если у тебя сайт на нескольких языках, сделай отдельные feed-маршруты: /ru/rss.xml и /en/rss.xml. И обязательно прописывай в каждом свой title и language:

return rss({
  title: 'Полина — записи',
  description: '...',
  site: context.site,
  customData: '<language>ru-RU</language>',
  items,
});

Sitemap для SSR-страниц

Интеграция @astrojs/sitemap работает только с теми страницами, которые попали в dist/ после билда. Если у тебя SSR-маршрут вроде /p/[slug] и страницы существуют в БД — интеграция о них не узнает. Решений два:

  • Сделать страницы полностью статичными через getStaticPaths, если данные доступны на момент билда.
  • Сгенерировать sitemap самостоятельно — отдельным маршрутом src/pages/sitemap.xml.ts, в котором ты сам перечислишь URL из БД.

Второй вариант я использую для блога с большой динамической частью. Выглядит так:

export async function GET() {
  const posts = await db.post.findMany({ where: { published: true } });
  const items = posts
    .map(
      (p) => `<url><loc>https://example.com/p/${p.slug}/</loc>` +
        `<lastmod>${p.updatedAt.toISOString()}</lastmod></url>`
    )
    .join('');
  const xml = `<?xml version="1.0" encoding="UTF-8"?>` +
    `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${items}</urlset>`;
  return new Response(xml, { headers: { 'Content-Type': 'application/xml' } });
}

Что проверить после деплоя

  • Открой /sitemap-index.xml и убедись, что в нём перечислены все нужные подкарты.
  • Открой /rss.xml в браузере — XML должен быть валидным, без обрезанных тегов.
  • Прогон через https://validator.w3.org/feed/ ловит большинство ошибок.
  • Sitemap отправь в Search Console (Google) и Вебмастер (Яндекс).

В сухом остатке

RSS и sitemap — пара файлов, которая стоит десяти минут работы и заметно улучшает индексацию и доступность. Я ставлю их в любой проект, где есть хотя бы три страницы контента, и обновляю по мере роста сайта. Без этого выходишь в эфир «без визитки» — найти такой сайт сложнее, а подписаться невозможно.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.