Метаданные

Next.js предоставляет API Metadata, который позволяет определять метаданные приложения (например, теги meta и link внутри HTML-элемента head) для улучшения SEO и возможности совместного использования в интернете.

Есть два способа добавления метаданных в ваше приложение:

  • Конфигурационные метаданные: Экспортируйте статический объект metadata или динамическую функцию generateMetadata в файле layout.js или page.js.
  • Файловые метаданные: Добавляйте статические или динамически генерируемые специальные файлы в сегменты маршрутов.

В обоих случаях Next.js автоматически сгенерирует соответствующие элементы <head> для ваших страниц. Вы также можете создавать динамические OG-изображения с помощью конструктора ImageResponse.

Статические метаданные

Для определения статических метаданных экспортируйте объект Metadata из файла layout.js или статического page.js.

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}
export const metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}

Все доступные параметры можно найти в справочнике API.

Динамические метаданные

Вы можете использовать функцию generateMetadata для получения метаданных, требующих динамических значений.

import type { Metadata, ResolvingMetadata } from 'next'

type Props = {
  params: { id: string }
  searchParams: { [key: string]: string | string[] | undefined }
}

export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // чтение параметров маршрута
  const id = params.id

  // получение данных
  const product = await fetch(`https://.../${id}`).then((res) => res.json())

  // опциональное использование и расширение (вместо замены) родительских метаданных
  const previousImages = (await parent).openGraph?.images || []

  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  }
}

export default function Page({ params, searchParams }: Props) {}
export async function generateMetadata({ params, searchParams }, parent) {
  // чтение параметров маршрута
  const id = params.id

  // получение данных
  const product = await fetch(`https://.../${id}`).then((res) => res.json())

  // опциональное использование и расширение (вместо замены) родительских метаданных
  const previousImages = (await parent).openGraph?.images || []

  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  }
}

export default function Page({ params, searchParams }) {}

Все доступные параметры можно найти в справочнике API.

Полезно знать:

  • И статические, и динамические метаданные через generateMetadata поддерживаются только в серверных компонентах.
  • Запросы fetch автоматически мемоизируются для одинаковых данных в generateMetadata, generateStaticParams, макетах, страницах и серверных компонентах. React cache можно использовать, если fetch недоступен.
  • Next.js будет ждать завершения получения данных внутри generateMetadata перед потоковой передачей интерфейса клиенту. Это гарантирует, что первая часть потокового ответа включает теги <head>.

Файловые метаданные

Для метаданных доступны следующие специальные файлы:

Их можно использовать для статических метаданных или генерировать программно.

Примеры реализации можно найти в справочниках API Metadata Files и Dynamic Image Generation.

Поведение

Файловые метаданные имеют более высокий приоритет и переопределяют конфигурационные метаданные.

Поля по умолчанию

Есть два тега meta, которые всегда добавляются, даже если маршрут не определяет метаданные:

  • meta charset tag устанавливает кодировку символов для сайта.
  • meta viewport tag устанавливает ширину области просмотра и масштаб для адаптации к разным устройствам.
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

Полезно знать: Вы можете переопределить тег viewport по умолчанию.

Порядок обработки

Метаданные обрабатываются в порядке от корневого сегмента до сегмента, ближайшего к конечному page.js. Например:

  1. app/layout.tsx (Корневой макет)
  2. app/blog/layout.tsx (Вложенный макет блога)
  3. app/blog/[slug]/page.tsx (Страница блога)

Объединение

Согласно порядку обработки, объекты метаданных, экспортированные из нескольких сегментов одного маршрута, поверхностно объединяются для формирования итоговых метаданных маршрута. Дублирующиеся ключи заменяются в соответствии с порядком.

Это означает, что метаданные с вложенными полями, такими как openGraph и robots, определенные в более раннем сегменте, перезаписываются последним сегментом, который их определяет.

Переопределение полей

app/layout.js
export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme is a...',
  },
}
app/blog/page.js
export const metadata = {
  title: 'Blog',
  openGraph: {
    title: 'Blog',
  },
}

// Результат:
// <title>Blog</title>
// <meta property="og:title" content="Blog" />

В этом примере:

  • title из app/layout.js заменяется на title из app/blog/page.js.
  • Все поля openGraph из app/layout.js заменяются в app/blog/page.js, потому что app/blog/page.js устанавливает метаданные openGraph. Обратите внимание на отсутствие openGraph.description.

Если вы хотите сохранить некоторые вложенные поля между сегментами, переопределяя другие, можно вынести их в отдельную переменную:

app/shared-metadata.js
export const openGraphImage = { images: ['http://...'] }
app/page.js
import { openGraphImage } from './shared-metadata'

export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'Home',
  },
}
app/about/page.js
import { openGraphImage } from '../shared-metadata'

export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'About',
  },
}

В этом примере OG-изображение используется совместно в app/layout.js и app/about/page.js, тогда как заголовки различаются.

Наследование полей

app/layout.js
export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme is a...',
  },
}
app/about/page.js
export const metadata = {
  title: 'About',
}

// Результат:
// <title>About</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme is a..." />

Примечания

  • title из app/layout.js заменяется на title из app/about/page.js.
  • Все поля openGraph из app/layout.js наследуются в app/about/page.js, потому что app/about/page.js не устанавливает метаданные openGraph.

Динамическая генерация изображений

Конструктор ImageResponse позволяет генерировать динамические изображения с использованием JSX и CSS. Это полезно для создания изображений для социальных сетей, таких как Open Graph и Twitter Cards.

ImageResponse использует Edge Runtime, и Next.js автоматически добавляет правильные заголовки для кэширования изображений на границе, что повышает производительность и уменьшает повторные вычисления.

Для использования импортируйте ImageResponse из next/og:

app/about/route.js
import { ImageResponse } from 'next/og'

export const runtime = 'edge'

export async function GET() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          textAlign: 'center',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        Hello world!
      </div>
    ),
    {
      width: 1200,
      height: 600,
    }
  )
}

ImageResponse хорошо интегрируется с другими API Next.js, включая Route Handlers и файловые метаданные. Например, вы можете использовать ImageResponse в файле opengraph-image.tsx для генерации Open Graph изображений во время сборки или динамически во время запроса.

ImageResponse поддерживает общие CSS-свойства, включая flexbox и абсолютное позиционирование, пользовательские шрифты, перенос текста, центрирование и вложенные изображения. Полный список поддерживаемых CSS-свойств.

Полезно знать:

  • Примеры доступны в Vercel OG Playground.
  • ImageResponse использует @vercel/og, Satori и Resvg для преобразования HTML и CSS в PNG.
  • Поддерживается только Edge Runtime. Стандартная Node.js среда не работает.
  • Поддерживаются только flexbox и подмножество CSS-свойств. Сложные макеты (например, display: grid) не работают.
  • Максимальный размер бандла — 500KB. Размер бандла включает JSX, CSS, шрифты, изображения и другие ресурсы. При превышении лимита рекомендуется уменьшить размер ресурсов или получать их во время выполнения.
  • Поддерживаются только форматы шрифтов ttf, otf и woff. Для максимальной скорости разбора предпочтительнее ttf или otf вместо woff.

JSON-LD

JSON-LD — это формат структурированных данных, который может использоваться поисковыми системами для понимания вашего контента. Например, с его помощью можно описать человека, событие, организацию, фильм, книгу, рецепт и другие типы сущностей.

Наша текущая рекомендация для JSON-LD — рендерить структурированные данные как тег <script> в компонентах layout.js или page.js. Например:

export default async function Page({ params }) {
  const product = await getProduct(params.id)

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    image: product.image,
    description: product.description,
  }

  return (
    <section>
      {/* Добавляем JSON-LD на страницу */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      {/* ... */}
    </section>
  )
}
export default async function Page({ params }) {
  const product = await getProduct(params.id)

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    image: product.image,
    description: product.description,
  }

  return (
    <section>
      {/* Добавляем JSON-LD на страницу */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      {/* ... */}
    </section>
  )
}

Вы можете проверить и протестировать структурированные данные с помощью Rich Results Test от Google или универсального Schema Markup Validator.

Для типизации JSON-LD в TypeScript можно использовать сторонние пакеты, такие как schema-dts:

import { Product, WithContext } from 'schema-dts'

const jsonLd: WithContext<Product> = {
  '@context': 'https://schema.org',
  '@type': 'Product',
  name: 'Next.js Sticker',
  image: 'https://nextjs.org/imgs/sticker.png',
  description: 'Dynamic at the speed of static.',
}