Пошук уроків, статей та іншого контенту
Створите метадані на основі параметрів маршруту, даних CMS або результатів API-запитів.
Метадані — це дані, які описують сторінку для браузера, пошукових систем і соціальних мереж:
заголовок вкладки браузера;
опис сторінки;
canonical URL;
Open Graph-дані для попереднього перегляду посилання;
Twitter Card-дані;
директиви для пошукових роботів.
У Next.js App Router статичні метадані можна оголосити через metadata, а динамічні — через асинхронну функцію generateMetadata.
Динамічні метадані потрібні, коли їхній вміст залежить від:
параметра маршруту, наприклад slug;
даних із CMS;
результату API-запиту;
конкретного товару, статті або профілю користувача.
Нехай сторінки статей мають такі адреси:
/blog/nextjs-metadata
/blog/server-components
/blog/typescript-basicsСтруктура маршруту в App Router:
app/
└── blog/
└── [slug]/
└── page.tsxЗначення [slug] доступне через параметр params.
У сучасних версіях Next.js параметри в серверних компонентах і generateMetadata потрібно отримувати асинхронно:
type PageProps = {
params: Promise<{
slug: string;
}>;
};generateMetadataФункція generateMetadata експортується з файлу сторінки або layout-файлу.
import type { Metadata } from "next";
export async function generateMetadata(): Promise<Metadata> {
return {
title: "Блог",
description: "Статті про веброзробку",
};
}Next.js виконає цю функцію на сервері та використає повернене значення для формування HTML-метатегів.
Функція може використовувати параметри маршруту:
import type { Metadata } from "next";
type PageProps = {
params: Promise<{
slug: string;
}>;
};
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const { slug } = await params;
return {
title: `Стаття: ${slug}`,
description: `Сторінка статті ${slug}`,
};
}Для типізації метаданих використовується тип Metadata з пакета next.
Розглянемо сторінку статті, яка отримує дані з CMS або зовнішнього API.
Нехай API повертає статтю в такому форматі:
{
"slug": "nextjs-metadata",
"title": "Динамічні метадані в Next.js",
"excerpt": "Як створювати метадані на основі параметрів маршруту та даних API.",
"content": "Вміст статті...",
"coverImage": "https://cdn.example.com/nextjs-metadata.jpg",
"publishedAt": "2026-08-17"
}Створимо тип і функцію для отримання статті:
// app/blog/[slug]/page.tsx
import type { Metadata } from "next";
import { notFound } from "next/navigation";
type Article = {
slug: string;
title: string;
excerpt: string;
content: string;
coverImage?: string;
publishedAt: string;
};
type PageProps = {
params: Promise<{
slug: string;
}>;
};
async function getArticle(slug: string): Promise<Article | null> {
const response = await fetch(
`${process.env.CMS_API_URL}/articles/${encodeURIComponent(slug)}`,
{
next: {
revalidate: 300,
},
},
);
if (response.status === 404) {
return null;
}
if (!response.ok) {
throw new Error("Не вдалося завантажити статтю");
}
return response.json();
}
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const { slug } = await params;
const article = await getArticle(slug);
if (!article) {
return {
title: "Статтю не знайдено",
description: "Запитувана стаття не існує.",
};
}
return {
title: article.title,
description: article.excerpt,
openGraph: {
title: article.title,
description: article.excerpt,
type: "article",
publishedTime: article.publishedAt,
images: article.coverImage
? [
{
url: article.coverImage,
width: 1200,
height: 630,
alt: article.title,
},
]
: undefined,
},
};
}
export default async function ArticlePage({ params }: PageProps) {
const { slug } = await params;
const article = await getArticle(slug);
if (!article) {
notFound();
}
return (
<main>
<h1>{article.title}</h1>
<p>{article.excerpt}</p>
<time dateTime={article.publishedAt}>{article.publishedAt}</time>
<div>{article.content}</div>
</main>
);
}У цьому прикладі:
slug отримується з URL.
getArticle(slug) завантажує відповідну статтю.
generateMetadata використовує заголовок, опис і зображення статті.
Та сама стаття завантажується для відображення сторінки.
Якщо статтю не знайдено, викликається notFound().
Змінна CMS_API_URL має бути задана в .env.local:
CMS_API_URL=https://cms.example.com/apiПараметр revalidate: 300 означає, що результат запиту може кешуватися протягом 300 секунд. Значення залежить від вимог конкретного застосунку та CMS.
Для відносних URL, зокрема canonical URL та зображень Open Graph, корисно задати metadataBase у кореневому layout:
// app/layout.tsx
import type { Metadata } from "next";
export const metadata: Metadata = {
metadataBase: new URL("https://example.com"),
title: {
default: "Мій блог",
template: "%s | Мій блог",
},
description: "Блог про веброзробку",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="uk">
<body>{children}</body>
</html>
);
}Тепер дочірня сторінка може вказати відносний canonical URL:
return {
title: article.title,
description: article.excerpt,
alternates: {
canonical: `/blog/${article.slug}`,
},
};За наявності metadataBase Next.js зможе перетворити його на повну адресу:
https://example.com/blog/nextjs-metadataУ layout можна оголосити шаблон:
export const metadata: Metadata = {
title: {
default: "Мій блог",
template: "%s | Мій блог",
},
};Якщо сторінка повертає:
return {
title: "Динамічні метадані в Next.js",
};фактичний заголовок буде:
Динамічні метадані в Next.js | Мій блогВодночас значення title може бути об'єктом і явно скасувати шаблон:
return {
title: {
absolute: article.title,
},
};У такому разі заголовок буде використано без суфікса з layout.
Open Graph-метадані визначають, як сторінка виглядатиме під час поширення в соціальних мережах і месенджерах.
Основні поля:
return {
openGraph: {
title: article.title,
description: article.excerpt,
url: `/blog/${article.slug}`,
type: "article",
images: [
{
url: article.coverImage,
width: 1200,
height: 630,
alt: article.title,
},
],
},
};Для статті тип article може містити дату публікації:
openGraph: {
type: "article",
publishedTime: article.publishedAt,
}Якщо зображення відсутнє, краще не створювати об'єкт images із порожнім або неправильним URL.
Метадані також мають коректно працювати, коли API не повернув запис.
Для неіснуючого ресурсу можна повернути спеціальні метадані:
if (!article) {
return {
title: "Сторінку не знайдено",
robots: {
index: false,
follow: false,
},
};
}Виклик notFound() у самій сторінці покаже стандартну або власну сторінку 404:
if (!article) {
notFound();
}Не варто підставляти undefined або порожні рядки без потреби:
return {
title: article?.title,
description: article?.excerpt,
};Краще явно обробити відсутній запис і повернути зрозумілі значення.
У generateMetadata і компоненті сторінки часто потрібні ті самі дані:
const article = await getArticle(slug);Якщо для обох викликів використовується однаковий fetch із тим самим URL та параметрами, Next.js може повторно використати кешований результат запиту в межах рендерингу.
Важливо, щоб запити були справді однаковими:
fetch(`${apiUrl}/articles/${slug}`, {
next: { revalidate: 300 },
});і:
fetch(`${apiUrl}/articles/${slug}`, {
next: { revalidate: 300 },
});Якщо URL або параметри відрізняються, це можуть бути різні запити.
generateMetadata має виконуватися на сервері. Її не можна експортувати з компонента, позначеного директивою "use client".
Неправильно:
"use client";
import type { Metadata } from "next";
export async function generateMetadata(): Promise<Metadata> {
return {
title: "Сторінка",
};
}Правильний підхід:
залишити page.tsx серверним компонентом;
отримати дані та сформувати метадані на сервері;
передати необхідні дані в Client Component через props.
// app/blog/[slug]/page.tsx
import ArticleInteractive from "./ArticleInteractive";
export default async function Page() {
const article = await getArticle("nextjs-metadata");
return <ArticleInteractive title={article.title} />;
}// app/blog/[slug]/ArticleInteractive.tsx
"use client";
type Props = {
title: string;
};
export default function ArticleInteractive({ title }: Props) {
return <button onClick={() => alert(title)}>Показати заголовок</button>;
}params.slug без awaitУ сучасних версіях Next.js params є Promise:
const { slug } = await params;а не:
const slug = params.slug;Якщо метадані залежать від даних статті, ці дані потрібно отримати і в generateMetadata. Інакше заголовок і опис не знатимуть про конкретний запис.
Open Graph-зображення має бути доступним за коректним URL. Порожній рядок або URL, що існує лише локально, може не працювати для зовнішніх сервісів.
404Не слід викликати response.json() для відповіді з помилкою, не перевіривши response.ok або response.status.
generateMetadata у Client ComponentФункція generateMetadata працює на сервері й не може бути частиною компонента з "use client".
Якщо layout уже має шаблон заголовка, не потрібно дублювати його на кожній сторінці:
return {
title: `${article.title} | Мій блог`,
};Краще повернути лише назву статті:
return {
title: article.title,
};Для динамічних метаданих у Next.js використовується generateMetadata.
Параметри динамічного маршруту можна використати для завантаження потрібного запису.
Дані для метаданих можуть надходити з CMS або зовнішнього API.
Тип Metadata допомагає правильно описати структуру метаданих.
openGraph дає змогу налаштувати вигляд посилання під час поширення.
metadataBase потрібен для коректного формування повних URL.
Відсутні дані потрібно обробляти окремо через запасні метадані та notFound().
generateMetadata має залишатися в серверному компоненті.