Open Graph в Next.js 15: превью лендинга для Telegram — KEL IT
Сайты 9 мин чтения

Open Graph в Next.js 15: превью лендинга для Telegram и соцсетей

Вы запустили лендинг с зелёными Core Web Vitals, JSON-LD и Partial Prerendering — а ссылка в Telegram показывает серый прямоугольник без картинки и обрезанный заголовок. Пользователь не кликает, менеджер по продажам пересылает ссылку в чат — и теряется половина смысла оффера. Для коммерческого лендинга Open Graph — не «косметика для Facebook», а часть конверсионной воронки: превью карточки влияет на CTR из мессенджеров, рассылок и органики в соцсетях.

В Next.js 15 App Router метаданные задаются через generateMetadata и файловые конвенции (opengraph-image.tsx). Это удобнее, чем ручные <meta> в _document, но легко получить дубли title/description, устаревший кеш OG-картинки или конфликт с canonical. В статье — production-подход для SEO-лендинга: статические и динамические OG-теги, генерация изображений 1200×630, особенности Telegram и проверка перед релизом.

Почему OG важнее, чем кажется SEO-специалисту

Google не использует og:title для ранжирования напрямую, но поведенческие сигналы от трафика из мессенджеров реальны. Telegram, WhatsApp, VK, Slack и Notion при расшаривании ссылки читают Open Graph (и иногда Twitter Cards как fallback). Если теги отсутствуют, парсер берёт первый <h1> и случайную картинку со страницы — часто логотип 64×64, растянутый в некрасивный thumbnail.

Элемент превьюРекомендуемый размерЧто ломается без настройки
og:image1200×630 px (1.91:1)Мелкий favicon или hero без crop
og:title40–60 символовОбрезка на мобильном Telegram
og:description120–155 символовПервый абзац <p> с навигацией
og:urlCanonical URLДубли с UTM без canonical

Для лендинга с одним оффером («Разработка сайтов за 14 дней») OG-карточка — мини-рекламный баннер. Заголовок должен совпадать по смыслу с <title>, но не обязан дословно повторять его: в <title> — ключ для поиска, в og:title — hook для человека в чате.

Twitter Cards (twitter:card, twitter:image) по-прежнему нужны: X и часть агрегаторов читают их первыми. Next.js 15 маппит поля openGraph и twitter из одного объекта Metadata — дублировать вручную не требуется, если настроить оба блока.

generateMetadata: статика, динамика и типичные ошибки

В App Router метаданные страницы задаются экспортом metadata (статика) или generateMetadata (динамика по params/searchParams):

// app/(marketing)/page.tsx — Server Component
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Разработка сайтов под ключ — срок 14 дней',
  description:
    'Next.js 15, Core Web Vitals в зелёной зоне, SEO и аналитика. Фиксированная смета и деплой на Vercel.',
  metadataBase: new URL('https://example.com'),
  alternates: {
    canonical: '/',
  },
  openGraph: {
    title: 'Сайт за 14 дней — без просадки по скорости',
    description:
      'Лендинг с INP ≤ 200 ms, JSON-LD и headless CMS. Обсудим проект за 15 минут.',
    url: '/',
    siteName: 'Example Studio',
    locale: 'ru_RU',
    type: 'website',
    images: [
      {
        url: '/og/landing-default.png',
        width: 1200,
        height: 630,
        alt: 'Пример SEO-лендинга на Next.js 15',
      },
    ],
  },
  twitter: {
    card: 'summary_large_image',
    title: 'Сайт за 14 дней — без просадки по скорости',
    description:
      'Лендинг с INP ≤ 200 ms, JSON-LD и headless CMS.',
    images: ['/og/landing-default.png'],
  },
};

Три ошибки, которые видим на аудитах:

  1. Нет metadataBase — относительные пути в og:image превращаются в https://localhost:3000/og/... на staging.
  2. Title template дублируется — в layout.tsx задан title.template: '%s | Studio', а в openGraph.title тот же суффикс добавлен вручную. Telegram показывает «Заголовок | Studio | Studio».
  3. generateMetadata без cache — при интеграции с headless CMS каждый запрос бота Telegram бьёт в API. Используйте fetch с { next: { revalidate: 3600 } } или статическую генерацию.

Для десятков landing pages по шаблону — динамический экспорт:

// app/(marketing)/services/[slug]/page.tsx
import type { Metadata } from 'next';
import { getServiceBySlug } from '@/lib/cms';

type Props = { params: Promise<{ slug: string }> };

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const service = await getServiceBySlug(slug);

  if (!service) return { title: 'Услуга не найдена' };

  return {
    title: service.seoTitle,
    description: service.seoDescription,
    alternates: { canonical: `/services/${slug}` },
    openGraph: {
      title: service.ogTitle ?? service.seoTitle,
      description: service.ogDescription ?? service.seoDescription,
      url: `/services/${slug}`,
      images: [{ url: `/og/services/${slug}`, width: 1200, height: 630 }],
    },
  };
}

Важно: робот Telegram не выполняет JavaScript. OG-теги должны быть в исходном HTML ответа сервера. Client Components и 'use client' на page.tsx не генерируют meta — только Server Components и generateMetadata.

Если нужна подобная разработка — напишите в Telegram.

OG-изображения: opengraph-image.tsx и @vercel/og

Статический PNG в /public/og/ — быстрый старт, но при 50+ услугах не масштабируется. Next.js 15 поддерживает file-based metadata:

app/
├── (marketing)/
│   ├── opengraph-image.tsx      # дефолт для секции
│   ├── page.tsx
│   └── services/
│       └── [slug]/
│           ├── opengraph-image.tsx
│           └── page.tsx

Пример генерации через ImageResponse (Satori + @vercel/og):

// app/(marketing)/opengraph-image.tsx
import { ImageResponse } from 'next/og';

export const runtime = 'edge';
export const alt = 'Example Studio — разработка сайтов';
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

export default function OgImage() {
  return new ImageResponse(
    (
      <div
        style={{
          width: '100%',
          height: '100%',
          display: 'flex',
          flexDirection: 'column',
          justifyContent: 'center',
          padding: 64,
          background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)',
          color: '#f8fafc',
          fontFamily: 'system-ui, sans-serif',
        }}
      >
        <div style={{ fontSize: 56, fontWeight: 700, lineHeight: 1.15 }}>
          Сайт за 14 дней
        </div>
        <div style={{ fontSize: 28, marginTop: 24, opacity: 0.85 }}>
          Next.js 15 · Core Web Vitals · SEO
        </div>
      </div>
    ),
    { ...size }
  );
}

Edge runtime здесь уместен: генерация лёгкая, cold start короткий, картинка кешируется CDN Vercel после первого запроса. Не смешивайте тяжёлые шрифты (multi-weight WOFF) — embed один weight через fetch локального TTF.

Для [slug]/opengraph-image.tsx принимайте params и подставляйте название услуги из CMS. Route handler app/og/[slug]/route.tsx — альтернатива, если нужны query-параметры (?title=), но file convention проще для SEO: URL предсказуемый, Next автоматически проставляет og:image в metadata.

Правила дизайна OG под Telegram:

  • Safe zone — текст и логотип в центральных 80%, края могут обрезаться в круглых preview.
  • Конtrast — мелкий серый текст на градиенте не читается в thumbnail 200 px шириной.
  • Без текста мелче 24 px в макете 1200×630 — после даунскейла станет нечитаемым.

Telegram, VK и отладка кеша превью

Telegram кеширует превью агрессивно. После деплоя новых OG-тегов ссылка в чате может показывать старую картинку сутками. Обходные пути:

  1. Добавить версионный query только для теста: ?v=2 — бот запросит свежий HTML. В production canonical и og:url оставляйте без query, иначе дубли URL.
  2. Использовать Telegram Bot API sendMessage с link_preview_options — для ботов, не для пользовательских шарингов.
  3. Проверять через opengraph.xyz, metatags.io или self-hosted curl -A "TelegramBot" — user-agent иногда влияет на отдачу.

VK и Facebook Meta Sharing Debugger сбрасывают кеш по кнопке «Scrape Again». Для VK отдельно проверьте og:image с HTTPS и размером ≥ 537×240.

Чеклист перед релизом лендинга:

# Исходный HTML содержит og:image с абсолютным URL
curl -sL https://example.com | grep -E 'og:(title|description|image|url)'

# Размер и тип картинки
curl -sI https://example.com/opengraph-image | grep -i content-type

В Search Console OG не отображается, но проверяйте URL Inspection → View crawled page — Google видит те же meta. Расхождение между title и og:title допустимо; расхождение между og:url и canonical — сигнал дубля.

Для мультиязычного лендинга добавьте openGraph.locale и alternates.languages — Telegram locale не переключает превью автоматически, но Facebook и LinkedIn используют hreflang-подсказки при выборе версии.

Связка с JSON-LD, PPR и headless CMS

OG и JSON-LD решают разные задачи: первый — превью в соцсетях, второй — rich snippets в SERP. Дублируйте смысл оффера, но не копируйте byte-в-byte: JSON-LD WebPage.description может быть длиннее, чем og:description.

При Partial Prerendering статическая оболочка включает <head> с metadata — бот Telegram получает OG мгновенно, без ожидания dynamic shell. Dynamic части страницы (личный кабинет, A/B-блок) не должны влиять на generateMetadata — выносите их в nested layouts без переопределения root metadata.

С Sanity, Contentful или Strapi храните ogTitle, ogDescription, ogImage отдельными полями в CMS. Редактор маркетинга меняет hook для Telegram, не трогая SEO title для Google. Revalidation (revalidateTag('service-${slug}')) после publish обновляет и HTML, и opengraph-image route.

shadcn/ui и framer-motion на OG не влияют — они client-side. Не тратьте время на отключение анимаций ради OG; тратьте на server-rendered head.

Нужна помощь? Telegram → или vic.kell@ya.ru

FAQ

Нужны ли отдельные og:title и document title?

Рекомендуется. <title> оптимизируется под ключевые слова и бренд (Разработка сайтов Москва | Studio). og:title — короткий hook для ленты (Сайт за 14 дней — смета в день обращения). Полное совпадение не ошибка, но упущенная возможность повысить CTR в мессенджерах.

Почему Telegram не видит og:image с Vercel?

Частые причины: относительный URL без metadataBase, редирект 307 на auth middleware, robots.txt блокирует /opengraph-image, image > 8 MB, или MIME-type не image/png / image/jpeg. Проверьте curl -sI URL с production-домена, не localhost.

Можно ли использовать hero WebP из next/image как og:image?

Не напрямую. next/image отдаёт оптимизированный URL с query-параметрами; не все боты его понимают. Для OG — статический PNG/JPEG или opengraph-image.tsx. Hero и OG могут визуально совпадать, но URL разные.

Как часто обновлять OG при контенте из CMS?

Синхронизируйте с ISR: revalidate: 3600 или on-demand revalidation при publish. Telegram-кеш живёт отдельно — после смены картинки предупредите команду, что preview в старых сообщениях не обновится.

Нужен ли og:video для лендинга?

Только если hero — полноценное промо-видео и вы целитесь в Facebook/LinkedIn video cards. Для типового B2B-лендинга summary_large_image достаточно; video утяжеляет парсинг и часто не поддерживается в Telegram.

Конфликтует ли opengraph-image.tsx с metadata.openGraph.images?

Next.js мерджит file-based и exported metadata. Если заданы оба, приоритет у file convention в той же папке. Держите один источник правды: либо PNG в metadata, либо opengraph-image.tsx, не оба сразу.

Заключение

Open Graph — часть упаковки лендинга наравне с LCP и JSON-LD. В Next.js 15 App Router достаточно metadataBase, продуманного generateMetadata, file-based opengraph-image.tsx на edge и проверки исходного HTML через curl. Telegram и VK не простят отсутствующую картинку — пользователь не кликнет, даже если сайт летает.

Настройте OG до запуска рекламы и рассылок: один раз сгенерируйте карточку 1200×630, проверьте absolute URL, сбросьте кеш в отладчиках — и каждая пересланная ссылка работает как мини-баннер вашего оффера.

KEL IT

Нужна разработка под ключ?

Я занимаюсь такими проектами профессионально. Telegram-боты, Mini Apps, сайты, мобильные и десктопные приложения. Расскажите о задаче — разберём и предложим решение.