Последние пять лет я погружен в Headless CMS. Моя страсть это превращать технические сложности в понятные решения. Сегодня я хочу поделиться опытом, как настраивать SEO в Strapi, Contentful и Sanity, чтобы ваш контент не просто существовал, а доминировал в поиске. Речь пойдет о динамических OG-тегах, канонических URL и хитростях пагинации через API. Погнали!
Почему Headless CMS это вызов для SEO?
Headless CMS дают свободу: фронтенд отделен от бэкенда, контент доставляется через API, а дизайн не ограничен шаблонами. Но именно эта гибкость становится ловушкой для SEO. Если метаданные, OG-теги или канонические ссылки настроены неправильно, поисковые роботы просто не поймут, как индексировать ваш сайт.
Мой подход: контролировать каждую деталь через API и предварительный рендеринг. Расскажу, как это работает в трех популярных CMS.
Динамические OG-теги
OG-теги (Open Graph) — это то, как ваш сайт выглядит в соцсетях. Если они статичны, вы теряете вовлечение. Решение — динамические OG-теги, которые меняются для каждой страницы.
Strapi: Плагины + кастомные контроллеры
В Strapi я использую плагин strapi-plugin-seo
, но часто дополняю его кастомным кодом. Например, для блога:
- Создаю поле
meta_image
в коллекции статей. - В контроллере добавляю логику: если
meta_image
не задан, подставляю обложку статьи. - Использую шаблоны (например, в Next.js), чтобы рендерить теги на основе данных из API:
// Пример для Next.js export async function getServerSideProps({ params }) { const article = await fetch(`/api/articles/${params.slug}`); return { props: { meta: { ogTitle: article.title + " | Блог", ogImage: article.meta_image || article.cover.url, }, }, }; }
Contentful: Поля-шаблоны и Webhooks
В Contentful я создаю отдельное поле SEO Template
типа JSON. В нем прописываю структуру OG-тегов с переменными вроде {title}
.
При публикации контента срабатывает вебхук на мой сервер, который парсит шаблон, подставляет данные и обновляет метаданные через API.
Sanity: GROQ-запросы и Portable Text
Sanity хорош своей гибкостью. Для OG-тегов я использую GROQ-запросы, чтобы получать данные даже из вложенных объектов:
// Запрос для получения OG-данных const query = `*[_type == "post" && slug.current == $slug][0]{ title, "ogImage": metaImage.asset->url, excerpt }`;
А затем передаю результат в компонент <Head>
в React или Vue.
Канонические URL: Как избежать дублей
Канонические теги — ваш щит против дублированного контента. Но в Headless CMS их легко испортить, особенно если фронтенд рендерит страницы на клиенте.
Правила, которые я соблюдаю:
- Абсолютные ссылки, а не относительные.
- Динамическое определение канонического URL на основе хоста и пути.
- Игнорирование параметров сортировки и фильтров (если они не меняют контент).
Реализация в Strapi:
Добавляю middleware в API, который добавляет каноническую ссылку в ответ:
// middleware/canonical.js module.exports = (req, res, next) => { const canonical = `https://${req.headers.host}${req.path}`; res.setHeader('Link', `<${canonical}>; rel="canonical"`); next(); };
В Contentful и Sanity:
Использую фронтенд-фреймворки (например, Nuxt.js), чтобы динамически вставлять тег <link rel="canonical">
в шапку страницы. Важно проверять, что URL генерируется корректно при SSR (Server-Side Rendering).
Пагинация через API
Пагинация в Headless CMS часто ломает SEO, особенно если реализована через бесконечный скролл или параметры ?page=2
. Вот как я это решаю.
Стратегия:
- Использовать HTTP-заголовки
Link
сrel="next"
иrel="prev"
. - Добавлять канонические ссылки на первую страницу пагинации.
- Для бесконечного скролла — пререндерить первые N страниц.
Пример для Strapi:
В контроллере списка статей возвращаю заголовки:
// api/article/controllers/article.js const page = parseInt(ctx.query.page) || 1; const nextPage = page + 1; ctx.set('Link', `<https://example.com/articles?page=${nextPage}>; rel="next"`);
В Sanity и Contentful:
Использую параметры limit
и offset
в API-запросах и передаю их в фронтенд. Например, в Gatsby для пререндеринга страниц:
// gatsby-node.js const articles = await sanityClient.fetch(`*[_type == "post"]`); const perPage = 10; const pages = Math.ceil(articles.length / perPage); Array.from({ length: pages }).forEach((_, i) => { createPage({ path: i === 0 ? `/blog` : `/blog/${i + 1}`, component: blogTemplate, context: { skip: i * perPage, }, }); });
Инструменты, которые меня спасают
- Screaming Frog. Для аудита канонических URL и метатегов после деплоя.
- Swagger/OpenAPI. Документирую эндпоинты метаданных, чтобы не запутаться.
- Google Structured Data Testing Tool. Проверяю, как роботы видят OG-теги.
Главный секрет в том, чтобы не надеяться на CMS, а взять контроль в свои руки. Настраивайте метаданные через API, тестируйте каждую страницу и помните даже в Headless-архитектуре SEO начинается с вашего кода.
Если у вас есть кейсы или вопросы пишите в комментарии.