Пошук уроків, статей та іншого контенту
Дізнаєтеся, як generateMetadata формує метадані програмно для сторінок і маршрутів.
generateMetadataУ Next.js з App Router метадані сторінки можна створити програмно за допомогою функції generateMetadata.
Це корисно, коли значення метаданих залежать від:
параметра маршруту;
даних із бази даних або API;
конкретного запису, товару чи профілю;
параметрів пошуку;
стану сторінки.
Наприклад, для маршруту /blog/nextjs-metadata можна сформувати заголовок на основі назви статті:
Next.js metadata: програмне формування метаданихФункція generateMetadata експортується з файлу
layout.tsximport type { Metadata } from 'next';
export async function generateMetadata(): Promise<Metadata> {
return {
title: 'Блог',
description: 'Статті про веброзробку',
};
}Значення, повернуте функцією, Next.js перетворює на HTML-елементи в секції <head>.
Для незмінних метаданих достатньо експортувати об’єкт metadata:
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Про нас',
description: 'Інформація про нашу команду',
};Якщо метадані потрібно обчислити під час побудови сторінки або на основі параметрів маршруту, використовуйте generateMetadata:
import type { Metadata } from 'next';
export async function generateMetadata(): Promise<Metadata> {
const title = 'Каталог товарів';
return {
title,
description: 'Перегляд доступних товарів',
};
}В одному сегменті маршруту не можна одночасно експортувати metadata і generateMetadata. Потрібно вибрати один підхід.
generateMetadataФункція може отримувати параметри маршруту та параметри пошукового рядка.
Для динамічного маршруту app/blog/[slug]/page.tsx параметри матимуть тип:
type PageParams = {
slug: string;
};У сучасних версіях Next.js параметри в App Router передаються як Promise, тому їх потрібно отримати через await:
import type { Metadata } from 'next';
type Props = {
params: Promise<{
slug: string;
}>;
};
export async function generateMetadata(
{ params }: Props,
): Promise<Metadata> {
const { slug } = await params;
return {
title: `Стаття: ${slug}`,
};
}У старіших версіях Next.js params могли бути звичайним об’єктом. Тоді await для них не був потрібен. Тип сигнатури має відповідати версії Next.js у вашому проєкті.
Розглянемо структуру:
app/
└── blog/
└── [slug]/
└── page.tsxМаршрут [slug] дозволяє відображати різні статті за адресами:
/blog/nextjs-metadata
/blog/app-router
/blog/server-componentsПриклад сторінки з програмними метаданими:
import type { Metadata } from 'next';
type Post = {
slug: string;
title: string;
description: string;
content: string;
};
type Props = {
params: Promise<{
slug: string;
}>;
};
const posts: Post[] = [
{
slug: 'nextjs-metadata',
title: 'Програмне формування метаданих у Next.js',
description: 'Як використовувати generateMetadata для динамічних сторінок.',
content: 'У цій статті розглядається Metadata API Next.js.',
},
{
slug: 'app-router',
title: 'Маршрутизація в App Router',
description: 'Основи файлової маршрутизації в сучасному Next.js.',
content: 'App Router використовує структуру папок для створення маршрутів.',
},
];
function getPost(slug: string): Post | undefined {
return posts.find((post) => post.slug === slug);
}
export async function generateMetadata(
{ params }: Props,
): Promise<Metadata> {
const { slug } = await params;
const post = getPost(slug);
if (!post) {
return {
title: 'Статтю не знайдено',
description: 'Запитана стаття не існує.',
};
}
return {
title: post.title,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
type: 'article',
},
};
}
export default async function BlogPostPage({ params }: Props) {
const { slug } = await params;
const post = getPost(slug);
if (!post) {
return <main>Статтю не знайдено</main>;
}
return (
<main>
<h1>{post.title}</h1>
<p>{post.description}</p>
<article>{post.content}</article>
</main>
);
}Для сторінки /blog/nextjs-metadata цей код сформує, зокрема:
<title>Програмне формування метаданих у Next.js</title>
<meta
name="description"
content="Як використовувати generateMetadata для динамічних сторінок."
>Значення openGraph використовуються для попереднього перегляду сторінки під час поширення її URL у соціальних мережах і месенджерах.
На практиці дані для заголовка часто надходять із API або бази даних. Функція generateMetadata може бути асинхронною:
import type { Metadata } from 'next';
type Product = {
id: string;
name: string;
description: string;
};
type Props = {
params: Promise<{
id: string;
}>;
};
async function getProduct(id: string): Promise<Product | null> {
const response = await fetch(`https://api.example.com/products/${id}`);
if (!response.ok) {
return null;
}
return response.json();
}
export async function generateMetadata(
{ params }: Props,
): Promise<Metadata> {
const { id } = await params;
const product = await getProduct(id);
if (!product) {
return {
title: 'Товар не знайдено',
};
}
return {
title: product.name,
description: product.description,
};
}
export default async function ProductPage({ params }: Props) {
const { id } = await params;
const product = await getProduct(id);
if (!product) {
return <main>Товар не знайдено</main>;
}
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
</main>
);
}Якщо сторінка та generateMetadata запитують однакові дані через fetch, Next.js може повторно використати результат однакового запиту. Попри це, для складнішої логіки отримання даних варто винести її в окрему функцію та організувати кешування відповідно до вимог застосунку.
У сторінці можна використовувати searchParams для формування метаданих на основі URL:
/products?category=booksПриклад:
import type { Metadata } from 'next';
type Props = {
searchParams: Promise<{
category?: string;
}>;
};
export async function generateMetadata(
{ searchParams }: Props,
): Promise<Metadata> {
const { category } = await searchParams;
const categoryTitle = category
? `Категорія: ${category}`
: 'Усі товари';
return {
title: categoryTitle,
description: `Список товарів. ${categoryTitle}`,
};
}
export default async function ProductsPage({ searchParams }: Props) {
const { category } = await searchParams;
return (
<main>
<h1>{category ? `Категорія: ${category}` : 'Усі товари'}</h1>
</main>
);
}searchParams особливо доречні для сторінок, де заголовок або опис залежать від фільтра, пошуку чи сортування.
Однак не всі варіанти URL потрібно відображати в SEO-метаданих. Наприклад, технічні параметри пагінації або сортування часто не повинні створювати окремі SEO-варіанти сторінки.
titleВластивість title може бути рядком або об’єктом із шаблоном:
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: {
default: 'Мій блог',
template: '%s | Мій блог',
},
};Тоді дочірня сторінка може повернути лише власну частину заголовка:
import type { Metadata } from 'next';
export async function generateMetadata(): Promise<Metadata> {
return {
title: 'Статті про Next.js',
};
}Підсумковий заголовок сторінки буде:
Статті про Next.js | Мій блогtitle.template зазвичай задають у кореневому layout.tsx, а конкретні сторінки повертають власний title.
Метадані можуть бути визначені на різних рівнях:
app/
├── layout.tsx
└── blog/
├── layout.tsx
└── [slug]/
└── page.tsxМетадані батьківського layout застосовуються до дочірніх маршрутів. Дочірній сегмент може перевизначити значення, наприклад title або description.
Кореневий layout:
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: {
default: 'Мій блог',
template: '%s | Мій блог',
},
description: 'Блог про сучасну веброзробку',
};Динамічна сторінка:
import type { Metadata } from 'next';
export async function generateMetadata(): Promise<Metadata> {
return {
title: 'Next.js',
description: 'Матеріали про розробку застосунків на Next.js',
};
}У результаті сторінка використає власні title і description, а інші несуперечливі метадані може отримати від батьківського layout.
Для вкладених об’єктів, таких як openGraph, потрібно явно повертати всі значення, які мають бути присутні на дочірній сторінці. Не варто розраховувати, що кожне вкладене поле автоматично об’єднається з батьківським.
MetadataТип Metadata підтримує багато полів. Найчастіше використовують:
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Каталог',
description: 'Каталог доступних товарів',
keywords: ['товари', 'каталог'],
authors: [{ name: 'Команда сайту' }],
robots: {
index: true,
follow: true,
},
alternates: {
canonical: 'https://example.com/products',
},
openGraph: {
title: 'Каталог',
description: 'Каталог доступних товарів',
url: 'https://example.com/products',
siteName: 'Мій магазин',
type: 'website',
},
};Для динамічних сторінок ці значення можна формувати на основі отриманих даних:
return {
title: product.name,
description: product.description,
alternates: {
canonical: `https://example.com/products/${product.id}`,
},
openGraph: {
title: product.name,
description: product.description,
url: `https://example.com/products/${product.id}`,
type: 'website',
},
};generateMetadata має коректно працювати, якщо запис не знайдено. Найпростіший варіант — повернути заголовок для сторінки помилки:
if (!post) {
return {
title: 'Сторінку не знайдено',
robots: {
index: false,
follow: false,
},
};
}Якщо сторінка також має показувати стандартну сторінку 404, у Server Component можна використати notFound():
import { notFound } from 'next/navigation';
export default async function BlogPostPage({ params }: Props) {
const { slug } = await params;
const post = getPost(slug);
if (!post) {
notFound();
}
return <h1>{post.title}</h1>;
}Для метаданих сторінки 404 Next.js використовує спеціальну обробку. Головне — не створювати успішні SEO-метадані для запису, якого не існує.
metadata і generateMetadata одночасноНеправильно:
export const metadata = {
title: 'Сторінка',
};
export async function generateMetadata() {
return {
title: 'Інший заголовок',
};
}В одному файлі потрібно залишити лише один із цих варіантів.
paramsУ сучасних версіях Next.js потрібно дочекатися параметрів:
const { slug } = await params;Якщо використовується версія Next.js, де params є звичайним об’єктом, тип і спосіб читання мають відповідати цій версії.
Неправильно одразу звертатися до властивостей, якщо API може повернути null:
const post = await getPost(slug);
return {
title: post.title,
};Безпечніше перевірити результат:
if (!post) {
return {
title: 'Статтю не знайдено',
};
}Якщо всі сторінки динамічного маршруту повертають один і той самий title, користувачі та пошукові системи не отримають інформації про конкретний запис.
Значення потрібно формувати з відповідних даних:
return {
title: post.title,
description: post.description,
};Метадані мають коротко описувати вміст сторінки. Не слід безпосередньо підставляти в title великі тексти або необроблені дані з URL.
use clientgenerateMetadata виконується на сервері й не може бути експортована з Client Component. Файл із цією функцією не повинен містити директиву:
'use client';Якщо інтерактивна частина сторінки є клієнтською, метадані все одно можна сформувати в серверному page.tsx або layout.tsx.
generateMetadata програмно формує метадані для сторінки або layout.
Функція може бути асинхронною та отримувати дані з API або іншого джерела.
Для динамічних маршрутів використовуйте params.
Для параметрів пошукового рядка сторінка може використовувати searchParams.
В одному сегменті маршруту не можна експортувати одночасно metadata і generateMetadata.
Дочірні маршрути можуть перевизначати метадані батьківських layout.
Для відсутніх даних потрібно повертати безпечні значення або використовувати notFound().
Метадані слід формувати на основі реального вмісту конкретної сторінки.