JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /Динамическая генерация метаданных:

Динамическая генерация метаданных: generateMetadata()

Модуль 4: Node.js, Next.js и Angular
9 уровень , 9 лекция
Открыта

1. Введение

Статические метаданные — это круто, когда у вас обычные страницы вроде "О нас" или "Контакты". Но что делать, если у вас интернет-магазин с сотнями товаров? Или блог, где каждая статья должна иметь свой уникальный заголовок и описание для поисковиков? Или, например, у вас есть страница профиля пользователя, и хочется, чтобы в <title> было его имя?

Вот тут и появляется задача: как сделать так, чтобы метаданные (title, description, open graph и т.д.) подставлялись автоматически, исходя из параметров страницы или данных из базы?

Ответ: Использовать функцию generateMetadata() в Next.js 15.

Как работает generateMetadata()

generateMetadata() — это специальная асинхронная функция, которую вы можете экспортировать из файла страницы (page.tsx) или layout-компонента (layout.tsx). Она вызывается Next.js на сервере при генерации страницы и возвращает объект метаданных, аналогичный тому, который вы бы описали статически.

Главное отличие:
Внутри generateMetadata() можно получать параметры маршрута, делать запросы к базе или API, использовать любые вычисления и возвращать метаданные "на лету".

Сигнатура


export async function generateMetadata(
  props: { params: Record<string, string>, searchParams: Record<string, string> }
): Promise<Metadata> {
  // ...
}

Пояснение:

  • params — параметры динамического маршрута (например, [id]).
  • searchParams — query-параметры из URL (например, ?page=2).
  • Возвращаемый объект должен соответствовать типу Metadata (тот же, что для статического metadata).

2. Пример: Динамический title для страницы товара

Давайте представим, что у нас интернет-магазин, и есть страница товара по адресу /product/[id]. Для SEO хочется, чтобы title был вида "Купить [название товара] — Магазин".

Шаг 1: Структура проекта


app/
  product/
    [id]/
      page.tsx

Шаг 2: Пример функции generateMetadata


// app/product/[id]/page.tsx

import type { Metadata } from 'next';

// Фейковая функция для примера — обычно тут будет запрос к базе или API
async function getProductById(id: string) {
  // В реальном приложении тут был бы fetch или DB-запрос
  const products = {
    '1': { name: 'Смартфон XPhone 42', description: 'Лучший смартфон года!' },
    '2': { name: 'Ноутбук UltraBook', description: 'Мощный ноутбук для работы и игр.' },
  };
  return products[id] || { name: 'Неизвестный товар', description: '' };
}

// Это и есть динамическая генерация метаданных
export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
  const product = await getProductById(params.id);

  return {
    title: `Купить ${product.name} — Магазин`,
    description: product.description,
    openGraph: {
      title: `Купить ${product.name} — Магазин`,
      description: product.description,
      // Можно добавить картинку и другие OG-поля
    },
  };
}

// Основной компонент страницы
export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProductById(params.id);

  return (
    <main>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </main>
  );
}

Что происходит:

  • При заходе на /product/1 Next.js вызывает generateMetadata, передает туда { params: { id: '1' } }.
  • Функция получает данные товара (пусть даже фейковые), формирует title и description на основе этих данных.
  • SEO-роботы, соцсети и браузеры увидят корректные метаданные.

3. Использование searchParams в generateMetadata

Иногда нужно подставлять метаданные в зависимости от query-параметров, например, для фильтрации или пагинации.


export async function generateMetadata({
  params,
  searchParams,
}: {
  params: { id: string },
  searchParams: { page?: string }
}): Promise<Metadata> {
  const page = searchParams.page || '1';
  return {
    title: `Товар ${params.id} — страница ${page}`,
    description: `Описание товара ${params.id}, страница ${page}`,
  };
}

4. Динамическая генерация Open Graph и других метаданных

Метаданные — это не только title и description. Можно динамически подставлять картинки для соцсетей, авторов, даты публикации и всё, что поддерживает объект Metadata.


export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
  const product = await getProductById(params.id);

  return {
    title: `Купить ${product.name} — Магазин`,
    description: product.description,
    openGraph: {
      title: `Купить ${product.name} — Магазин`,
      description: product.description,
      images: [`https://example.com/images/products/${params.id}.jpg`],
      type: 'product',
    },
    twitter: {
      card: 'summary_large_image',
      title: `Купить ${product.name}`,
      description: product.description,
      images: [`https://example.com/images/products/${params.id}.jpg`],
    },
  };
}

Где можно использовать generateMetadata

  • В файле страницы (page.tsx) — для генерации метаданных, специфичных для конкретной страницы.
  • В layout.tsx — для генерации метаданных, которые касаются целого раздела (например, всех страниц профиля пользователя).
  • Можно использовать одновременно в layout и page — метаданные будут умно объединяться (page "перебивает" layout).

5. Практика: Динамический title для блога

Представим, что у нас есть блог, и каждый пост доступен по адресу /blog/[slug]. Нам нужно, чтобы title страницы был названием статьи, а description — её аннотацией.


// app/blog/[slug]/page.tsx

// Фейковая функция для примера
async function getPostBySlug(slug: string) {
  const posts = {
    'nextjs-15': { title: 'Что нового в Next.js 15', excerpt: 'Обзор всех фич новой версии.' },
    'react-hooks': { title: 'React Hooks: полное руководство', excerpt: 'Учимся использовать хуки по-взрослому.' },
  };
  return posts[slug] || { title: 'Пост не найден', excerpt: '' };
}

export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
  const post = await getPostBySlug(params.slug);

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
    },
  };
}

export default async function BlogPostPage({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug);

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.excerpt}</p>
    </article>
  );
}

6. Особенности и нюансы динамических метаданных

  • Асинхронность
    generateMetadata() может быть асинхронной: вы можете делать fetch, обращаться к базе, читать файлы, всё что угодно. Но помните: чем дольше работает эта функция, тем дольше генерируется страница!
  • Кеширование
    • По умолчанию Next.js кеширует результат generateMetadata() для одинаковых параметров (например, для /product/1).
    • Если вы хотите отключить кеширование (например, если данные часто меняются), используйте опцию cache: "no-store" в fetch или настройте revalidate.
  • Ошибки
    Если внутри generateMetadata() произойдет ошибка (например, не найден товар), можно вернуть дефолтные метаданные, чтобы не "ломать" страницу.
  • Использование в Server Components
    generateMetadata() всегда вызывается на сервере, даже если страница клиентская ("use client"). Это значит, что можно безопасно обращаться к серверным ресурсам.

7. Типичные ошибки при работе с generateMetadata

Ошибка №1: Путают статический объект metadata и функцию generateMetadata

Часто новички пытаются одновременно объявить и объект export const metadata = {...} и функцию export async function generateMetadata(). В этом случае Next.js отдаёт приоритет функции, а объект игнорирует. Не стоит дублировать оба варианта, выберите тот, который вам нужен.

Ошибка №2: Отсутствие обработки несуществующих данных

Если вы делаете fetch по параметру, а данных нет (например, товара с таким id не существует), не возвращайте undefined — это вызовет ошибку. Лучше вернуть дефолтные метаданные: title: "Товар не найден".

Ошибка №3: Использование client-only данных

Пытаются использовать внутри generateMetadata что-то, что есть только на клиенте (например, window, localStorage). Не работает! Всё, что внутри generateMetadata, должно быть серверным.

Ошибка №4: Слишком долгие запросы

Если в generateMetadata делаете очень медленный запрос, пользователи будут ждать дольше загрузки страницы. Старайтесь оптимизировать эти запросы или использовать кеширование.

Ошибка №5: Неправильная работа с params и searchParams

Иногда путают параметры маршрута (params) и query-параметры (searchParams). Например, если динамический сегмент называется [slug], то его значение будет в params.slug, а не в searchParams.slug.

1
Задача
Модуль 4: Node.js, Next.js и Angular, 9 уровень, 9 лекция
Недоступна
Список популярных статей с серверной загрузкой и кешированием
Список популярных статей с серверной загрузкой и кешированием
1
Задача
Модуль 4: Node.js, Next.js и Angular, 9 уровень, 9 лекция
Недоступна
Каталог фильмов с отображением состояния загрузки
Каталог фильмов с отображением состояния загрузки
1
Задача
Модуль 4: Node.js, Next.js и Angular, 9 уровень, 9 лекция
Недоступна
Список книг с обработкой ошибок и ручным управлением кешем
Список книг с обработкой ошибок и ручным управлением кешем
1
Задача
Модуль 4: Node.js, Next.js и Angular, 9 уровень, 9 лекция
Недоступна
Каталог товаров с инкрементальной регенерацией (ISR) и SEO-метаданными
Каталог товаров с инкрементальной регенерацией (ISR) и SEO-метаданными
1
Задача
Модуль 4: Node.js, Next.js и Angular, 9 уровень, 9 лекция
Недоступна
Мини-блог: динамические страницы, кеширование по тегу и fallback loading/error
Мини-блог: динамические страницы, кеширование по тегу и fallback loading/error
3
Опрос
Metadata API, 9 уровень, 9 лекция
Недоступен
Metadata API
Metadata API
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ