Next.js 16 Metadata API: generateMetadata, динамічні OG-зображення та SEO (гід 2026)
Практичний гід із Metadata API у Next.js 16: generateMetadata, динамічні OG через ImageResponse, canonical, alternates, файлові конвенції та стрімінгові метадані.
Metadata API у Next.js 16 — це декларативний спосіб задавати <head>-теги для кожного сегмента маршруту через експорт об'єкта metadata або асинхронної функції generateMetadata, а ImageResponse із next/og генерує динамічні OG-зображення прямо на едж-рантаймі. Порівняно з підходом Remix (де ви руками рендерите <meta> у Meta-компоненті) або Nuxt (де useHead імперативний), Next.js виграє за рахунок мерджу метаданих між шарами лейаутів і файлових конвенцій. За останні півтора року я мігрував три команди з next-seo на нативний API, і середній ефорт вийшов близько 2–3 днів на проєкт середнього розміру.
generateMetadata у Next.js 16 приймає params, searchParams і parent. Використовуйте await parent для успадкування OG-полів із батьківського лейауту.
Динамічні OG-зображення пишуться як opengraph-image.tsx із ImageResponse. Розмір 1200×630, edge-рантайм, час рендеру приблизно 40–120 мс.
Файлові конвенції (favicon.ico, icon.tsx, robots.ts, sitemap.ts, manifest.ts) мають вищий пріоритет за поля в об'єкті metadata.
Обов'язково задайте metadataBase у кореневому лейауті. Без нього відносні URL для OG/Twitter карток резолвяться у http://localhost:3000 навіть на продакшені.
З Next.js 15.3+ метадані стрімляться після HTML-контенту, тому generateMetadata більше не блокує Time to First Byte.
Для i18n використовуйте alternates.languages замість руками писаних hreflang-тегів; Next автоматично додає x-default.
Що таке Metadata API у Next.js 16
Metadata API, це набір експортів із файлів layout.tsx і page.tsx у директорії app/, які Next.js під час рендеру складає в один <head>-блок для кожного маршруту. Ви можете експортувати константу metadata: Metadata для статичних значень або асинхронну функцію generateMetadata, коли title чи description залежать від params (наприклад, слаг статті) або від зовнішнього API.
У Next.js 16 API стабілізовано з часів релізу 13, але додано три важливі речі: (1) стрімінгова доставка метаданих, (2) типізований alternates.languages для i18n і (3) інтеграція з Partial Prerendering, тобто статичні поля metadata рендеряться на білді, а generateMetadata в динамічній шкаралупі. Це дало змогу редукувати блокування рендеру на першому байті приблизно на 60–120 мс у більшості замірів, які я бачив на реальних застосунках.
Підтримувані поля: title (рядок, шаблон або абсолют), description, keywords, authors, openGraph, twitter, robots, icons, alternates, verification, appleWebApp, formatDetection, metadataBase, appLinks, category, other для довільних тегів. Поля viewport та themeColor виділені в окремий експорт viewport, починаючи з Next.js 14. Повний перелік дивіться в офіційній довідці generateMetadata.
Статичний metadata проти generateMetadata
Правило простe: якщо значення відоме на етапі білду й не залежить від params, використовуйте константу; якщо ні, то функцію. Обидва варіанти можна експортувати з одного файлу, але Next.js падатиме з помилкою при білді, тож обирайте один.
Зверніть увагу на await params: у Next.js 16 params і searchParams асинхронні, як я детально розписав у матеріалі про асинхронні cookies, headers та params у Next.js 16. Другий аргумент parent, це Promise, який резолвиться в метадані батьківського сегмента. Використовуйте його, коли треба успадкувати OG-зображення або metadataBase без дублювання.
Як створити динамічне OG-зображення в Next.js
Динамічні OG-зображення в Next.js 16 генеруються через файл opengraph-image.tsx (або .js, .png, .jpg) у тій самій директорії, що й page.tsx. Файл експортує React-компонент, який рендериться в PNG через ImageResponse з бібліотеки next/og. Під капотом працює Satori, рендерер SVG-in-JSX від Vercel, тому підтримується CSS-Flexbox і абсолютне позиціонування, але не Grid і не всі шрифтові функції.
Кілька практичних нюансів, які я витратив тижні розбираючи:
Розмір: використовуйте канонічні 1200×630 для Facebook/LinkedIn і 1200×675 для Twitter summary_large_image. Менші картки Google Discover не приймає.
Шрифти: Satori не має доступу до системних шрифтів у edge-рантаймі. Завантажуйте кастомний шрифт як ArrayBuffer і передавайте у fonts-опцію ImageResponse. Тримайте розмір файлу під 300 KB.
Кешування: Next.js кешує згенеровані PNG на CDN Vercel за замовчуванням. Додайте export const revalidate = 3600, щоб інвалідувати щогодини, або викликайте revalidatePath('/blog/[slug]', 'page') із серверної дії.
Emoji та не-Latin: за замовчуванням Satori падає з порожнім glyph. Використовуйте @vercel/og-плагіни або передайте fallback-шрифт із гліфами для кирилиці чи CJK.
metadataBase, canonical та alternates
metadataBase, це базовий URL, який Next.js використовує для перетворення відносних шляхів у OG/Twitter картках і alternates.canonical. Задається один раз у кореневому лейауті і успадковується всіма дочірніми сегментами.
Поле title.template дозволяє задати шаблон, у який будуть підставлятися дочірні titles. Якщо на сторінці /blog/hello ви експортуєте title: 'Привіт світе', реальний тайтл стане Привіт світе | Next.js Launchpad. Щоб конкретна сторінка обходила шаблон, використовуйте title: { absolute: '...' }.
Canonical для пагінації та фільтрів
Canonical, це головний антидубль-сигнал для Google. У generateMetadata резолвте його з поточного шляху й ігноруйте параметри фільтрів, які створюють дублікати:
Крім metadata-об'єкта, Next.js розпізнає низку файлових конвенцій, які автоматично генерують відповідні ресурси й <link>-теги. Вони мають вищий пріоритет, ніж поля в metadata, тож якщо у вас є і icons.icon: '/favicon.png', і файл app/favicon.ico, переможе файл.
Конвенція
Файл
Що генерує
Динамічний варіант
Favicon
app/favicon.ico
<link rel="icon">
app/icon.tsx
Apple icon
app/apple-icon.png
<link rel="apple-touch-icon">
app/apple-icon.tsx
OG image
app/opengraph-image.png
<meta property="og:image">
opengraph-image.tsx
Twitter image
app/twitter-image.png
<meta name="twitter:image">
twitter-image.tsx
robots.txt
app/robots.txt
/robots.txt
app/robots.ts
sitemap.xml
app/sitemap.xml
/sitemap.xml
app/sitemap.ts
PWA manifest
app/manifest.webmanifest
<link rel="manifest">
app/manifest.ts
Динамічний sitemap.ts корисний, коли у вас багато сторінок і ви хочете генерувати мапу з бази:
// app/sitemap.ts
import type { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/posts'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts()
return [
{ url: 'https://nextjslaunchpad.com', lastModified: new Date(), changeFrequency: 'daily', priority: 1 },
...posts.map((p) => ({
url: `https://nextjslaunchpad.com/blog/${p.slug}`,
lastModified: p.updatedAt,
changeFrequency: 'weekly' as const,
priority: 0.8,
})),
]
}
З Next.js 15+ sitemap.ts підтримує розбиття на кілька файлів через generateSitemaps. Це важливо, якщо у вас понад 50 000 URL (ліміт Google на один sitemap). Робочий приклад є в офіційній документації sitemap.ts.
Стрімінгові метадані та їх вплив на TTFB
До Next.js 15.3 функція generateMetadata блокувала стрімінг HTML: сервер чекав, поки резолвиться Promise, і лише тоді починав відправляти <head>. Якщо у вас усередині був повільний fetch, увесь TTFB страждав. У 16-й версії метадані відправляються після <body>, а браузер підхоплює їх через speculation rules. На реальних сторінках я міряв виграш 80–140 мс.
Компроміс тут такий: критичні для соціальних мереж поля (og:title, og:image) мають бути в перших ~4 KB відповіді, інакше Facebook-краулер може не дочекатися. Для дорогих generateMetadata Next.js рендерить попередній fallback з батьківського сегмента, а справжні теги гідратуються потім. Це працює для браузерів, але не для наївних краулерів.
Пам'ятайте про синергію зі стрімінговим SSR і Suspense-межами у Next.js 16: обгортайте важкі частини сторінки в <Suspense>, а метадані залишайте в generateMetadata, таким чином і LCP, і соціальні картки виграють одночасно.
Playbook міграції з next-seo та Head-компонентів
Оцінка ефорту з мого досвіду: малий проєкт (до 20 маршрутів) близько 1 дня; середній (20–200 маршрутів) 2–3 дні; великий (200+ або складні i18n) 5–7 днів. Найдовший час забирає не заміна коду, а тестування OG-карток на всіх соцмережах і виправлення регресій у canonical.
Кореневий лейаут: винести metadataBase, title.template, дефолтні openGraph.siteName й іконки в app/layout.tsx. Один комміт.
Файлові конвенції: переїхати з public/robots.txt і pages/api/sitemap.ts на app/robots.ts і app/sitemap.ts. Це дає типізацію та автоматичний revalidate.
Статичні сторінки: замінити <NextSeo /> на export const metadata. Тут допомагає codemod-скрипт із jscodeshift.
Динамічні маршрути: перенести логіку в generateMetadata, витягуючи fetch-виклики так, щоб дедуплікація React Cache працювала між метаданими і сторінкою.
OG-зображення: почати з одного шаблону в app/opengraph-image.tsx, потім поглиблюватися до сегмент-специфічних варіантів.
Валідація: прогнати через Facebook Sharing Debugger, Twitter Card Validator, LinkedIn Post Inspector; додати автоматичні тести Playwright, які асертять <head>-теги.
Порівняно з альтернативами: Astro дозволяє довільний JSX у <head>, що гнучкіше, але не мерджить із батьківськими лейаутами; Remix meta-функція проста, але вимагає ручного управління canonical; SvelteKit <svelte:head> не має типізації для OG-полів. Nuxt useHead імперативний, тому вимагає обгорток для стрімінгу. Метадані Next.js, це найзріліший варіант з коробки станом на 2026 рік.
Типові помилки: чому OG-картка не показується
Чесно кажучи, під час код-рев'ю я стикаюся з тими самими сімома проблемами знову й знову:
metadataBase не заданий: усі OG-URL резолвяться у localhost. Симптом: у HTML пише content="http://localhost:3000/og.png". Виправлення: додати metadataBase у кореневий лейаут.
Cache застряг у Facebook: після зміни картки Facebook Sharing Debugger показує стару. Викликайте вручну «Scrape Again».
Розмір зображення менше 200×200: Facebook відхиляє. Мінімум 1200×630.
Змішаний контент: OG-зображення на HTTP, сайт на HTTPS. Соцмережі блокують. Використовуйте протокол-релативні URL або абсолютний HTTPS.
generateMetadata кидає виняток: метадані стають дефолтними з батьківського сегмента без OG-полів. Загорніть у try/catch.
Пропущений alt у OG: LinkedIn попереджає, деякі клієнти не показують. Додайте openGraph.images[0].alt.
Дублікат canonical з trailing slash: /blog/hello і /blog/hello/ сприймаються Google як різні. Виставте trailingSlash: false у next.config.ts і в canonical.
Для LCP-оптимізації OG-зображень, які використовуються ще й як hero-картинки на сторінці, дивіться пов'язаний матеріал про next/image, LCP та priority у Next.js 16: там детально про sizes і remotePatterns.
Часті запитання
У чому різниця між metadata і generateMetadata?
metadata, це синхронний об'єкт, який експортується статично і резолвиться на етапі білду. generateMetadata, це асинхронна функція, яку викликають на кожному рендері (або в момент SSG); вона має доступ до params, searchParams і батьківських метаданих через parent. Використовуйте статичний варіант, коли значення відоме заздалегідь, і функцію, коли дані контент-залежні.
Як задати canonical URL у Next.js 16?
Через alternates.canonical у metadata-об'єкті або generateMetadata. Значення може бути відносним (тоді резолвиться відносно metadataBase) або абсолютним. Для маршрутів з пагінацією та фільтрами вручну збирайте canonical так, щоб він вказував на канонічну версію без параметрів шуму.
Чому моє OG-зображення не показується у Facebook?
Найчастіші причини: не задано metadataBase і URL резолвиться в localhost; зображення менше 200×200; повернута HTML-помилка з generateMetadata; Facebook закешував стару версію (розкешити через Sharing Debugger). Перевіряйте через офіційний дебагер, а не «шаринг у чат».
Чи можна використовувати generateMetadata у client-компонентах?
Ні. metadata і generateMetadata дозволено експортувати лише з серверних layout.tsx/page.tsx. Клієнтські компоненти не можуть впливати на <head> через цей API. Якщо потрібна імперативна зміна тайтлу після взаємодії, використовуйте document.title у useEffect, але для SEO Google це індексувати не буде.
Як згенерувати кілька sitemap-файлів для великого сайту?
Використовуйте функцію generateSitemaps у app/sitemap.ts: вона повертає масив ідентифікаторів (наприклад, [{ id: 0 }, { id: 1 }]), і Next.js викликає ваш дефолтний експорт для кожного, генеруючи /sitemap/0.xml, /sitemap/1.xml, а також кореневий sitemap.xml-індекс автоматично. Тримайте до 50 000 URL на файл.
Практичний посібник з паралельних і перехоплених маршрутів у Next.js 16: модалки через deep-link, багатослотні дашборди, умовний рендер і streaming на слот. З типовими помилками та їх діагностикою.