SEO-метаданные для карточки проекта: как мы это сделали

Когда я делал страницу проекта Borisov AI на Next.js, выяснилось, что каждый проект должен иметь собственные метаданные для поисковиков и соцсетей. Задача казалась простой, но дьявол, как всегда, был в деталях.
Сначала я подумал: «Просто добавлю generateMetadata в page.tsx проекта и готово». Но потом понял — нужно учитывать локализацию. Если пользователь смотрит проект на русском, в <title> должен быть русский текст. На английском — английский. А заголовок проекта приходит из Strapi API, где может быть либо одно, либо другое.
Решение оказалось элегантным: вместо дублирования логики я переиспользовал существующий fetch-запрос к Strapi. Next.js автоматически дедублирует одинаковые запросы в рамках одного рендера, поэтому данные проекта загружаются один раз — и для страницы, и для метаданных. Это сэкономило не только код, но и время отклика.
Потом пришлось решить, откуда брать изображение для og:image. Нельзя же просто взять первую попавшуюся картинку. В каждом проекте в Strapi может быть thumbnail, и я использовал существующую функцию getStrapiMediaUrl для его обработки. Если thumbnail отсутствует — падаем на /og-default.png, и это работает.
Интересный момент с canonical и hreflang — они нужны, чтобы поисковики понимали, что русская и английская версии одного проекта — это не дубли, а альтернативные локали. Без этого Google может наказать за дублированный контент.
Вот что получилось:
- Per-project <title> с названием из Strapi
- <meta description> из поля description API
- Open Graph и Twitter Card с изображением
- Правильная разметка для мультиязычности
Сама реализация — всего ~30 строк в generateMetadata, но она охватывает все кейсы: есть проект — есть метаданные, нет проекта — есть fallback.
Факт о Cypress: оказывается, фреймворк для e2e-тестирования работает похоже на подростка — непредсказуем, требует постоянного внимания и иногда отказывается сотрудничать без явной переконфигурации 😄
Метаданные
- Session ID:
- grouped_borisovai-site_20260522_1418
- Branch:
- feat/postgres-strapi-pgvector
- Dev Joke
- Что общего у Cypress и подростка? Оба непредсказуемы и требуют постоянного внимания