Урок 04

SEO в Next.js: Open Graph и OG-изображения

Что такое Open Graph

Open Graph — протокол для превью ссылок в соцсетях.

Что даёт:

  • Красивое превью в Telegram, VK, Facebook.
  • Больше кликов.
  • Лучший CTR.

Правило: без OG — ссылка выглядит серой.

Метатеги OG

Основные:

<meta property="og:type" content="website" />
<meta property="og:title" content="Заголовок" />
<meta property="og:description" content="Описание" />
<meta property="og:image" content="https://example.com/og.jpg" />
<meta property="og:url" content="https://example.com/page" />
<meta property="og:site_name" content="SKILLS-DEV.RU" />
<meta property="og:locale" content="ru_RU" />

Что означает:

  • og:type — тип контента.
  • og:title — заголовок.
  • og:description — описание.
  • og:image — картинка.
  • og:url — URL.
  • og:site_name — имя сайта.

Типы og:type

  • website — сайт.
  • article — статья.
  • product — товар.
  • profile — профиль.
  • video.movie — видео.
  • music.song — музыка.

В Next.js

Metadata API:

export const metadata: Metadata = {
    openGraph: {
        type: 'website',
        title: 'Заголовок',
        description: 'Описание',
        url: 'https://example.com',
        siteName: 'SKILLS-DEV.RU',
        locale: 'ru_RU',
        images: [
            {
                url: 'https://example.com/og.jpg',
                width: 1200,
                height: 630,
                alt: 'Превью',
            },
        ],
    },
};

Что даёт: все OG-теги в <head>.

Размеры OG-изображений

Рекомендуемые:

  • 1200×630 — оптимально.
  • 1200×1200 — для Instagram.
  • 1080×1080 — квадрат.
  • 600×315 — минимум.
  • Соотношение 1.91:1 — стандарт.

Правило: 1200×630 — безопасно.

Что на OG-картинке

Хорошо:

  • Заголовок крупно.
  • Логотип бренда.
  • Ключевая картинка.
  • Контрастный фон.

Плохо:

  • Много текста.
  • Мелкий текст.
  • Сложный фон.
  • Обрезка по краям.

Генерация OG-изображений

Next.js умеет генерировать на лету через ImageResponse.

Файлы:

  • opengraph-image.tsx — для всех в папке.
  • opengraph-image.tsx в [slug] — для динамики.
  • twitter-image.tsx — для Twitter.

Правило: Next автоматически подставит в metadata.

Static OG

app/opengraph-image.tsx:

import { ImageResponse } from 'next/og';

export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

export default async function Image() {
    return new ImageResponse(
        (
            <div
                style={{
                    width: '100%',
                    height: '100%',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    background: '#c1442e',
                    color: 'white',
                    fontSize: 80,
                    fontWeight: 800,
                }}
            >
                SKILLS-DEV.RU
            </div>
        ),
        { ...size },
    );
}

Что даёт: OG для всего сайта.

Динамический OG

app/tracks/[slug]/opengraph-image.tsx:

import { ImageResponse } from 'next/og';

export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';
export const alt = 'Превью трека';

export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
    const { slug } = await params;
    const track = await fetch(`https://api.example.com/tracks/${slug}`).then((r) => r.json());

    return new ImageResponse(
        (
            <div
                style={{
                    width: '100%',
                    height: '100%',
                    display: 'flex',
                    flexDirection: 'column',
                    justifyContent: 'space-between',
                    padding: 80,
                    background: 'linear-gradient(135deg, #c1442e 0%, #1a1a1c 100%)',
                    color: 'white',
                }}
            >
                <div style={{ fontSize: 32, opacity: 0.8 }}>SKILLS-DEV.RU</div>
                <div style={{ fontSize: 72, fontWeight: 800, lineHeight: 1.1 }}>{track.title}</div>
                <div style={{ fontSize: 32, opacity: 0.8 }}>{track.courses_count} курсов</div>
            </div>
        ),
        { ...size },
    );
}

Что даёт: уникальная OG-картинка для каждого трека.

Шрифты

По умолчанию — только system fonts.

Для кастомных:

import { ImageResponse } from 'next/og';
import { readFile } from 'fs/promises';
import { join } from 'path';

export default async function Image() {
    const interSemiBold = await readFile(
        join(process.cwd(), 'assets/Inter-SemiBold.ttf'),
    );

    return new ImageResponse(
        (
            <div style={{ fontFamily: 'Inter', fontSize: 80 }}>Привет</div>
        ),
        {
            width: 1200,
            height: 630,
            fonts: [
                {
                    name: 'Inter',
                    data: interSemiBold,
                    style: 'normal',
                    weight: 600,
                },
            ],
        },
    );
}

⚠️ Кириллица требует шрифта с поддержкой.

Правило: Inter, Roboto, Open Sans — ок.

Динамический фон

Изображение из API:

<div
    style={{
        width: '100%',
        height: '100%',
        backgroundImage: `url(${track.coverImage})`,
        backgroundSize: 'cover',
        display: 'flex',
        alignItems: 'flex-end',
        padding: 80,
    }}
>
    <div style={{ fontSize: 72, color: 'white', textShadow: '0 4px 16px rgba(0,0,0,0.8)' }}>
        {track.title}
    </div>
</div>

⚠️ Внешние URL — требуют проверки, что Next может их загрузить.

Twitter Cards

Metadata:

export const metadata: Metadata = {
    twitter: {
        card: 'summary_large_image',
        title: 'Заголовок',
        description: 'Описание',
        images: ['https://example.com/twitter.jpg'],
        creator: '@username',
    },
};

Типы card:

  • summary — маленькая.
  • summary_large_image — большая.
  • player — видео.
  • app — приложение.

Telegram

Telegram — использует OG-теги.

Что важно:

  • og:title — заголовок.
  • og:description — описание.
  • og:image — картинка.
  • Кеш — обновляется не сразу.

Как обновить:

  • @WebpageBot в Telegram.
  • Прислать ссылку — бот обновит.

VK

VK — использует OG.

Что важно:

  • og:image — не меньше 400×200.
  • og:title — до 80 символов.
  • og:description — до 160.

Facebook

Debugger:

developers.facebook.com/tools/debug/

Что даёт:

  • Проверить OG-теги.
  • Обновить кеш.
  • Посмотреть превью.

Проверка

Инструменты:

  • Facebook Sharing Debugger.
  • Twitter Card Validator.
  • Telegram @WebpageBot.
  • VK — через отправку.
  • LinkedIn Post Inspector.

Пример в Laravel

Blade:

<meta property="og:type" content="website">
<meta property="og:title" content="{{ $track->title }}">
<meta property="og:description" content="{{ $track->description }}">
<meta property="og:image" content="{{ asset('storage/' . $track->cover) }}">
<meta property="og:url" content="{{ url()->current() }}">
<meta property="og:site_name" content="SKILLS-DEV.RU">
<meta property="og:locale" content="ru_RU">

Правило: динамические значения из переменных.

Кеширование

Проблема: OG-картинка генерируется на каждый запрос.

Решение: кешировать.

Next.js сам кеширует статические.

Динамические — через headers:

export const revalidate = 3600;

Правило: 1 час для динамики.

Практика

1. Добавьте Open Graph в layout.**

2. Создайте opengraph-image.tsx статическую.**

3. Создайте [slug]/opengraph-image.tsx динамическую.**

4. Настройте шрифт с кириллицей.**

5. Проверьте через Facebook Debugger.

6. Проверьте в Telegram.

7. Проверьте в VK.

8. Обновите кеш.**

Частые ошибки

1. Нет og:image:

Серая ссылка

Решение: добавить.

2. Картинка 400×200:

Обрезается

Решение: 1200×630.

3. Относительный URL:

og:image: /img.jpg

Решение: абсолютный.

4. Кириллица без шрифта:

Квадратики

Решение: шрифт с поддержкой.

5. Много текста:

Непонятно

Решение: заголовок + бренд.

6. Кеш Telegram:

Старое превью

Решение: @WebpageBot.

7. Не проверять:

Ошибки незаметны

Решение: Debugger.

8. Нет twitter:

В Twitter без превью

Решение: добавить twitter.

Итог

  • Open Graph — превью в соцсетях.
  • Типы: website, article, product.
  • Размер: 1200×630.
  • Next.js — Metadata API.
  • opengraph-image.tsx — генерация.
  • [slug]/opengraph-image.tsx — динамика.
  • Шрифт с кириллицей.
  • Twitter Cards — отдельно.
  • Telegram, VK — используют OG.
  • Проверка через Debugger.
  • Кеш обновлять.

Дальше — SEO в React: react-helmet и аналоги.