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.
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 и аналоги.