generateMetadata

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

Объект metadata

Для определения статических метаданных экспортируйте объект 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() {}

Полный список поддерживаемых опций смотрите в разделе Поля метаданных.

Функция generateMetadata

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

import type { Metadata, ResolvingMetadata } from 'next'

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

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

  // получение данных
  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 } = await params

  // получение данных
  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 }) {}

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

  • Метаданные можно добавлять в файлы layout.js и page.js.
  • Next.js автоматически обработает метаданные и создаст соответствующие теги <head> для страницы.
  • Экспорт объекта metadata и функции generateMetadata поддерживается только в Server Components.
  • Нельзя экспортировать одновременно объект metadata и функцию generateMetadata из одного сегмента маршрута.
  • Запросы fetch внутри generateMetadata автоматически мемоизируются для одинаковых данных между generateMetadata, generateStaticParams, Layouts, Pages и Server Components.
  • React cache можно использовать, если fetch недоступен.
  • Файловые метаданные имеют более высокий приоритет и переопределяют объект metadata и функцию generateMetadata.

Справочник

Параметры

Функция generateMetadata принимает следующие параметры:

  • props - Объект, содержащий параметры текущего маршрута:

    • params - Объект, содержащий параметры динамического маршрута от корневого сегмента до сегмента, из которого вызывается generateMetadata. Примеры:

      МаршрутURLparams
      app/shop/[slug]/page.js/shop/1{ slug: '1' }
      app/shop/[tag]/[item]/page.js/shop/1/2{ tag: '1', item: '2' }
      app/shop/[...slug]/page.js/shop/1/2{ slug: ['1', '2'] }
    • searchParams - Объект, содержащий параметры поиска текущего URL. Примеры:

      URLsearchParams
      /shop?a=1{ a: '1' }
      /shop?a=1&b=2{ a: '1', b: '2' }
      /shop?a=1&a=2{ a: ['1', '2'] }
  • parent - Промис с разрешёнными метаданными из родительских сегментов маршрута.

Возвращаемое значение

generateMetadata должна возвращать объект Metadata, содержащий одно или несколько полей метаданных.

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

  • Если метаданные не зависят от информации во время выполнения, их следует определять с помощью статического объекта metadata, а не generateMetadata.
  • Запросы fetch автоматически мемоизируются для одинаковых данных между generateMetadata, generateStaticParams, Layouts, Pages и Server Components. React cache можно использовать, если fetch недоступен.
  • searchParams доступны только в сегментах page.js.
  • Методы Next.js redirect() и notFound() также можно использовать внутри generateMetadata.

Поля метаданных

Поддерживаются следующие поля:

title

Атрибут title используется для установки заголовка документа. Может быть определён как простая строка или необязательный шаблонный объект.

Строка
layout.js | page.js
export const metadata = {
  title: 'Next.js',
}
<head> output
<title>Next.js</title>
default

title.default можно использовать для предоставления резервного заголовка дочерним сегментам маршрута, которые не определяют title.

app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    default: 'Acme',
  },
}
app/about/page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {}

// Вывод: <title>Acme</title>
template

title.template можно использовать для добавления префикса или суффикса к titles, определённым в дочерних сегментах маршрута.

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    template: '%s | Acme',
    default: 'Acme', // требуется по умолчанию при создании шаблона
  },
}
export const metadata = {
  title: {
    template: '%s | Acme',
    default: 'Acme', // требуется по умолчанию при создании шаблона
  },
}
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'About',
}

// Вывод: <title>About | Acme</title>
export const metadata = {
  title: 'About',
}

// Вывод: <title>About | Acme</title>

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

  • title.template применяется к дочерним сегментам маршрута, а не к сегменту, в котором он определён. Это означает:

    • title.default обязателен при добавлении title.template.
    • title.template, определённый в layout.js, не будет применяться к title, определённому в page.js того же сегмента маршрута.
    • title.template, определённый в page.js, не имеет эффекта, потому что страница всегда является конечным сегментом (у неё нет дочерних сегментов маршрута).
  • title.template не имеет эффекта, если маршрут не определил title или title.default.

absolute

title.absolute можно использовать для предоставления заголовка, который игнорирует title.template, установленный в родительских сегментах.

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    template: '%s | Acme',
  },
}
export const metadata = {
  title: {
    template: '%s | Acme',
  },
}
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    absolute: 'About',
  },
}

// Вывод: <title>About</title>
export const metadata = {
  title: {
    absolute: 'About',
  },
}

// Вывод: <title>About</title>

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

  • layout.js

    • title (строка) и title.default определяют заголовок по умолчанию для дочерних сегментов (которые не определяют свой собственный title). Они будут дополнять title.template из ближайшего родительского сегмента, если он существует.
    • title.absolute определяет заголовок по умолчанию для дочерних сегментов. Он игнорирует title.template из родительских сегментов.
    • title.template определяет новый шаблон заголовка для дочерних сегментов.
  • page.js

    • Если страница не определяет свой собственный заголовок, будет использоваться заголовок ближайшего родителя.
    • title (строка) определяет заголовок маршрута. Он будет дополнять title.template из ближайшего родительского сегмента, если он существует.
    • title.absolute определяет заголовок маршрута. Он игнорирует title.template из родительских сегментов.
    • title.template не имеет эффекта в page.js, потому что страница всегда является конечным сегментом маршрута.

description

layout.js | page.js
export const metadata = {
  description: 'The React Framework for the Web',
}
<head> output
<meta name="description" content="The React Framework for the Web" />

Другие поля

layout.js | page.js
export const metadata = {
  generator: 'Next.js',
  applicationName: 'Next.js',
  referrer: 'origin-when-cross-origin',
  keywords: ['Next.js', 'React', 'JavaScript'],
  authors: [{ name: 'Seb' }, { name: 'Josh', url: 'https://nextjs.org' }],
  creator: 'Jiachi Liu',
  publisher: 'Sebastian Markbåge',
  formatDetection: {
    email: false,
    address: false,
    telephone: false,
  },
}
<head> output
<meta name="application-name" content="Next.js" />
<meta name="author" content="Seb" />
<link rel="author" href="https://nextjs.org" />
<meta name="author" content="Josh" />
<meta name="generator" content="Next.js" />
<meta name="keywords" content="Next.js,React,JavaScript" />
<meta name="referrer" content="origin-when-cross-origin" />
<meta name="color-scheme" content="dark" />
<meta name="creator" content="Jiachi Liu" />
<meta name="publisher" content="Sebastian Markbåge" />
<meta name="format-detection" content="telephone=no, address=no, email=no" />

metadataBase

metadataBase - это удобная опция для установки базового префикса URL для полей metadata, которые требуют полного URL.

  • metadataBase позволяет URL-полям metadata, определённым в текущем сегменте маршрута и ниже, использовать относительный путь вместо обязательного абсолютного URL.
  • Относительный путь поля будет объединён с metadataBase для формирования полного URL.
layout.js | page.js
export const metadata = {
  metadataBase: new URL('https://acme.com'),
  alternates: {
    canonical: '/',
    languages: {
      'en-US': '/en-US',
      'de-DE': '/de-DE',
    },
  },
  openGraph: {
    images: '/og-image.png',
  },
}
<head> output
<link rel="canonical" href="https://acme.com" />
<link rel="alternate" hreflang="en-US" href="https://acme.com/en-US" />
<link rel="alternate" hreflang="de-DE" href="https://acme.com/de-DE" />
<meta property="og:image" content="https://acme.com/og-image.png" />

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

  • metadataBase обычно устанавливается в корневом app/layout.js, чтобы применяться к URL-полям metadata во всех маршрутах.
  • Все URL-поля metadata, требующие абсолютных URL, могут быть настроены с помощью опции metadataBase.
  • metadataBase может содержать поддомен, например https://app.acme.com, или базовый путь, например https://acme.com/start/from/here.
  • Если поле metadata предоставляет абсолютный URL, metadataBase будет проигнорирован.
  • Использование относительного пути в URL-поле metadata без настройки metadataBase вызовет ошибку сборки.
  • Next.js нормализует дублирующиеся слеши между metadataBase (например, https://acme.com/) и относительным полем (например, /path) до одного слеша (например, https://acme.com/path).

Композиция URL

Композиция URL отдаёт предпочтение намерениям разработчика перед семантикой обхода директорий по умолчанию.

  • Конечные слеши между metadataBase и полями metadata нормализуются.
  • "Абсолютный" путь в поле metadata (который обычно заменяет весь путь URL) рассматривается как "относительный" путь (начиная с конца metadataBase).

Например, для следующего metadataBase:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  metadataBase: new URL('https://acme.com'),
}
export const metadata = {
  metadataBase: new URL('https://acme.com'),
}

Любые поля metadata, которые наследуют вышеуказанный metadataBase и устанавливают собственное значение, будут разрешены следующим образом:

Поле metadataРазрешённый URL
/https://acme.com
./https://acme.com
paymentshttps://acme.com/payments
/paymentshttps://acme.com/payments
./paymentshttps://acme.com/payments
../paymentshttps://acme.com/payments
https://beta.acme.com/paymentshttps://beta.acme.com/payments

openGraph

layout.js | page.js
export const metadata = {
  openGraph: {
    title: 'Next.js',
    description: 'The React Framework for the Web',
    url: 'https://nextjs.org',
    siteName: 'Next.js',
    images: [
      {
        url: 'https://nextjs.org/og.png', // Должен быть абсолютным URL
        width: 800,
        height: 600,
      },
      {
        url: 'https://nextjs.org/og-alt.png', // Должен быть абсолютным URL
        width: 1800,
        height: 1600,
        alt: 'Мой альтернативный текст',
      },
    ],
    videos: [
      {
        url: 'https://nextjs.org/video.mp4', // Должен быть абсолютным URL
        width: 800,
        height: 600,
      },
    ],
    audio: [
      {
        url: 'https://nextjs.org/audio.mp3', // Должен быть абсолютным URL
      },
    ],
    locale: 'en_US',
    type: 'website',
  },
}
<head> output
<meta property="og:title" content="Next.js" />
<meta property="og:description" content="The React Framework for the Web" />
<meta property="og:url" content="https://nextjs.org/" />
<meta property="og:site_name" content="Next.js" />
<meta property="og:locale" content="en_US" />
<meta property="og:image" content="https://nextjs.org/og.png" />
<meta property="og:image:width" content="800" />
<meta property="og:image:height" content="600" />
<meta property="og:image" content="https://nextjs.org/og-alt.png" />
<meta property="og:image:width" content="1800" />
<meta property="og:image:height" content="1600" />
<meta property="og:image:alt" content="Мой альтернативный текст" />
<meta property="og:video" content="https://nextjs.org/video.mp4" />
<meta property="og:video:width" content="800" />
<meta property="og:video:height" content="600" />
<meta property="og:audio" content="https://nextjs.org/audio.mp3" />
<meta property="og:type" content="website" />
layout.js | page.js
export const metadata = {
  openGraph: {
    title: 'Next.js',
    description: 'The React Framework for the Web',
    type: 'article',
    publishedTime: '2023-01-01T00:00:00.000Z',
    authors: ['Seb', 'Josh'],
  },
}
<head> output
<meta property="og:title" content="Next.js" />
<meta property="og:description" content="The React Framework for the Web" />
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2023-01-01T00:00:00.000Z" />
<meta property="article:author" content="Seb" />
<meta property="article:author" content="Josh" />

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

  • Для изображений Open Graph может быть удобнее использовать файловый Metadata API. Вместо синхронизации конфигурации с реальными файлами, файловый API автоматически сгенерирует правильные метаданные.

robots

layout.tsx | page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  robots: {
    index: true,
    follow: true,
    nocache: false,
    googleBot: {
      index: true,
      follow: true,
      noimageindex: false,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1,
    },
  },
}
<head> output
<meta name="robots" content="index, follow" />
<meta
  name="googlebot"
  content="index, follow, max-video-preview:-1, max-image-preview:large, max-snippet:-1"
/>

icons

Полезно знать: Рекомендуем использовать файловый Metadata API для иконок, где это возможно. Вместо синхронизации конфигурации с реальными файлами, файловый API автоматически сгенерирует правильные метаданные.

layout.js | page.js
export const metadata = {
  icons: {
    icon: '/icon.png',
    shortcut: '/shortcut-icon.png',
    apple: '/apple-icon.png',
    other: {
      rel: 'apple-touch-icon-precomposed',
      url: '/apple-touch-icon-precomposed.png',
    },
  },
}
<head> output
<link rel="shortcut icon" href="/shortcut-icon.png" />
<link rel="icon" href="/icon.png" />
<link rel="apple-touch-icon" href="/apple-icon.png" />
<link
  rel="apple-touch-icon-precomposed"
  href="/apple-touch-icon-precomposed.png"
/>
layout.js | page.js
export const metadata = {
  icons: {
    icon: [
      { url: '/icon.png' },
      new URL('/icon.png', 'https://example.com'),
      { url: '/icon-dark.png', media: '(prefers-color-scheme: dark)' },
    ],
    shortcut: ['/shortcut-icon.png'],
    apple: [
      { url: '/apple-icon.png' },
      { url: '/apple-icon-x3.png', sizes: '180x180', type: 'image/png' },
    ],
    other: [
      {
        rel: 'apple-touch-icon-precomposed',
        url: '/apple-touch-icon-precomposed.png',
      },
    ],
  },
}
<head> output
<link rel="shortcut icon" href="/shortcut-icon.png" />
<link rel="icon" href="/icon.png" />
<link rel="icon" href="https://example.com/icon.png" />
<link rel="icon" href="/icon-dark.png" media="(prefers-color-scheme: dark)" />
<link rel="apple-touch-icon" href="/apple-icon.png" />
<link
  rel="apple-touch-icon-precomposed"
  href="/apple-touch-icon-precomposed.png"
/>
<link
  rel="apple-touch-icon"
  href="/apple-icon-x3.png"
  sizes="180x180"
  type="image/png"
/>

Полезно знать: Мета-теги msapplication-* больше не поддерживаются в Chromium-сборках Microsoft Edge и больше не нужны.

themeColor

Устарело: Опция themeColor в metadata устарела в Next.js 14. Вместо неё используйте viewport конфигурацию.

colorScheme

Устарело: Опция colorScheme в metadata устарела в Next.js 14. Вместо неё используйте viewport конфигурацию.

manifest

Манифест веб-приложения, как определено в спецификации Web Application Manifest.

layout.js | page.js
export const metadata = {
  manifest: 'https://nextjs.org/manifest.json',
}
<head> output
<link rel="manifest" href="https://nextjs.org/manifest.json" />

twitter

Спецификация Twitter используется (удивительно) не только для X (ранее известного как Twitter).

Подробнее о Twitter Card markup reference.

layout.js | page.js
export const metadata = {
  twitter: {
    card: 'summary_large_image',
    title: 'Next.js',
    description: 'The React Framework for the Web',
    siteId: '1467726470533754880',
    creator: '@nextjs',
    creatorId: '1467726470533754880',
    images: ['https://nextjs.org/og.png'], // Должен быть абсолютным URL
  },
}
<head> output
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site:id" content="1467726470533754880" />
<meta name="twitter:creator" content="@nextjs" />
<meta name="twitter:creator:id" content="1467726470533754880" />
<meta name="twitter:title" content="Next.js" />
<meta name="twitter:description" content="The React Framework for the Web" />
<meta name="twitter:image" content="https://nextjs.org/og.png" />
layout.js | page.js
export const metadata = {
  twitter: {
    card: 'app',
    title: 'Next.js',
    description: 'The React Framework for the Web',
    siteId: '1467726470533754880',
    creator: '@nextjs',
    creatorId: '1467726470533754880',
    images: {
      url: 'https://nextjs.org/og.png',
      alt: 'Next.js Logo',
    },
    app: {
      name: 'twitter_app',
      id: {
        iphone: 'twitter_app://iphone',
        ipad: 'twitter_app://ipad',
        googleplay: 'twitter_app://googleplay',
      },
      url: {
        iphone: 'https://iphone_url',
        ipad: 'https://ipad_url',
      },
    },
  },
}
<head> output
<meta name="twitter:site:id" content="1467726470533754880" />
<meta name="twitter:creator" content="@nextjs" />
<meta name="twitter:creator:id" content="1467726470533754880" />
<meta name="twitter:title" content="Next.js" />
<meta name="twitter:description" content="The React Framework for the Web" />
<meta name="twitter:card" content="app" />
<meta name="twitter:image" content="https://nextjs.org/og.png" />
<meta name="twitter:image:alt" content="Next.js Logo" />
<meta name="twitter:app:name:iphone" content="twitter_app" />
<meta name="twitter:app:id:iphone" content="twitter_app://iphone" />
<meta name="twitter:app:id:ipad" content="twitter_app://ipad" />
<meta name="twitter:app:id:googleplay" content="twitter_app://googleplay" />
<meta name="twitter:app:url:iphone" content="https://iphone_url" />
<meta name="twitter:app:url:ipad" content="https://ipad_url" />
<meta name="twitter:app:name:ipad" content="twitter_app" />
<meta name="twitter:app:name:googleplay" content="twitter_app" />

viewport

Устарело: Опция viewport в metadata устарела в Next.js 14. Вместо неё используйте viewport конфигурацию.

verification

layout.js | page.js
export const metadata = {
  verification: {
    google: 'google',
    yandex: 'yandex',
    yahoo: 'yahoo',
    other: {
      me: ['my-email', 'my-link'],
    },
  },
}
<head> output
<meta name="google-site-verification" content="google" />
<meta name="y_key" content="yahoo" />
<meta name="yandex-verification" content="yandex" />
<meta name="me" content="my-email" />
<meta name="me" content="my-link" />

appleWebApp

layout.js | page.js
export const metadata = {
  itunes: {
    appId: 'myAppStoreID',
    appArgument: 'myAppArgument',
  },
  appleWebApp: {
    title: 'Apple Web App',
    statusBarStyle: 'black-translucent',
    startupImage: [
      '/assets/startup/apple-touch-startup-image-768x1004.png',
      {
        url: '/assets/startup/apple-touch-startup-image-1536x2008.png',
        media: '(device-width: 768px) and (device-height: 1024px)',
      },
    ],
  },
}
<head> output
<meta
  name="apple-itunes-app"
  content="app-id=myAppStoreID, app-argument=myAppArgument"
/>
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Apple Web App" />
<link
  href="/assets/startup/apple-touch-startup-image-768x1004.png"
  rel="apple-touch-startup-image"
/>
<link
  href="/assets/startup/apple-touch-startup-image-1536x2008.png"
  media="(device-width: 768px) and (device-height: 1024px)"
  rel="apple-touch-startup-image"
/>
<meta
  name="apple-mobile-web-app-status-bar-style"
  content="black-translucent"
/>

alternates

layout.js | page.js
export const metadata = {
  alternates: {
    canonical: 'https://nextjs.org',
    languages: {
      'en-US': 'https://nextjs.org/en-US',
      'de-DE': 'https://nextjs.org/de-DE',
    },
    media: {
      'only screen and (max-width: 600px)': 'https://nextjs.org/mobile',
    },
    types: {
      'application/rss+xml': 'https://nextjs.org/rss',
    },
  },
}
<head> output
<link rel="canonical" href="https://nextjs.org" />
<link rel="alternate" hreflang="en-US" href="https://nextjs.org/en-US" />
<link rel="alternate" hreflang="de-DE" href="https://nextjs.org/de-DE" />
<link
  rel="alternate"
  media="only screen and (max-width: 600px)"
  href="https://nextjs.org/mobile"
/>
<link
  rel="alternate"
  type="application/rss+xml"
  href="https://nextjs.org/rss"
/>
layout.js | page.js
export const metadata = {
  appLinks: {
    ios: {
      url: 'https://nextjs.org/ios',
      app_store_id: 'app_store_id',
    },
    android: {
      package: 'com.example.android/package',
      app_name: 'app_name_android',
    },
    web: {
      url: 'https://nextjs.org/web',
      should_fallback: true,
    },
  },
}
<head> output
<meta property="al:ios:url" content="https://nextjs.org/ios" />
<meta property="al:ios:app_store_id" content="app_store_id" />
<meta property="al:android:package" content="com.example.android/package" />
<meta property="al:android:app_name" content="app_name_android" />
<meta property="al:web:url" content="https://nextjs.org/web" />
<meta property="al:web:should_fallback" content="true" />

archives

Описывает коллекцию записей, документов или других материалов, представляющих исторический интерес (источник).

layout.js | page.js
export const metadata = {
  archives: ['https://nextjs.org/13'],
}
<head> output
<link rel="archives" href="https://nextjs.org/13" />

assets

layout.js | page.js
export const metadata = {
  assets: ['https://nextjs.org/assets'],
}
<head> output
<link rel="assets" href="https://nextjs.org/assets" />

bookmarks

layout.js | page.js
export const metadata = {
  bookmarks: ['https://nextjs.org/13'],
}
<head> output
<link rel="bookmarks" href="https://nextjs.org/13" />

category

layout.js | page.js
export const metadata = {
  category: 'technology',
}
<head> output
<meta name="category" content="technology" />

facebook

Вы можете подключить приложение Facebook или аккаунт Facebook к вашей веб-странице для определённых Facebook Social Plugins Документация Facebook

Полезно знать: Можно указать либо appId, либо admins, но не оба одновременно.

layout.js | page.js
export const metadata = {
  facebook: {
    appId: '12345678',
  },
}
<head> output
<meta property="fb:app_id" content="12345678" />
layout.js | page.js
export const metadata = {
  facebook: {
    admins: '12345678',
  },
}
<head> output
<meta property="fb:admins" content="12345678" />

Если нужно сгенерировать несколько мета-тегов fb:admins, можно использовать массив.

layout.js | page.js
export const metadata = {
  facebook: {
    admins: ['12345678', '87654321'],
  },
}
<head> output
<meta property="fb:admins" content="12345678" />
<meta property="fb:admins" content="87654321" />

pinterest

Вы можете включить или отключить Pinterest Rich Pins на вашей веб-странице.

layout.js | page.js
export const metadata = {
  pinterest: {
    richPin: true,
  },
}
<head> output
<meta name="pinterest-rich-pin" content="true" />

other

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

layout.js | page.js
export const metadata = {
  other: {
    custom: 'meta',
  },
}
<head> output
<meta name="custom" content="meta" />

Если нужно сгенерировать несколько мета-тегов с одинаковым ключом, можно использовать массив.

layout.js | page.js
export const metadata = {
  other: {
    custom: ['meta1', 'meta2'],
  },
}
<head> output
<meta name="custom" content="meta1" /> <meta name="custom" content="meta2" />

Неподдерживаемые метаданные

Следующие типы метаданных в настоящее время не имеют встроенной поддержки. Однако их всё ещё можно отрендерить в самом layout или странице.

Типы

Вы можете добавить проверку типов к метаданным, используя тип Metadata. Если вы используете встроенный плагин TypeScript в вашей IDE, вам не нужно добавлять тип вручную, но вы всё равно можете явно указать его при необходимости.

Объект metadata

layout.tsx | page.tsx
import type { Metadata } from 'next'

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

Функция generateMetadata

Обычная функция
layout.tsx | page.tsx
import type { Metadata } from 'next'

export function generateMetadata(): Metadata {
  return {
    title: 'Next.js',
  }
}
Асинхронная функция
layout.tsx | page.tsx
import type { Metadata } from 'next'

export async function generateMetadata(): Promise<Metadata> {
  return {
    title: 'Next.js',
  }
}
С параметрами сегментов
layout.tsx | page.tsx
import type { Metadata } from 'next'

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

export function generateMetadata({ params, searchParams }: Props): Metadata {
  return {
    title: 'Next.js',
  }
}

export default function Page({ params, searchParams }: Props) {}
С родительскими метаданными
layout.tsx | page.tsx
import type { Metadata, ResolvingMetadata } from 'next'

export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  return {
    title: 'Next.js',
  }
}
JavaScript-проекты

Для JavaScript-проектов вы можете использовать JSDoc для добавления проверки типов.

layout.js | page.js
/** @type {import("next").Metadata} */
export const metadata = {
  title: 'Next.js',
}
МетаданныеРекомендация
<meta http-equiv="...">Используйте соответствующие HTTP-заголовки через redirect(), Middleware, Security Headers
<base>Рендерите тег непосредственно в макете или странице.
<noscript>Рендерите тег непосредственно в макете или странице.
<style>Подробнее о стилях в Next.js.
<script>Подробнее об использовании скриптов.
<link rel="stylesheet" />Импортируйте таблицы стилей напрямую в макет или страницу.
<link rel="preload />Используйте метод preload ReactDOM
<link rel="preconnect" />Используйте метод preconnect ReactDOM
<link rel="dns-prefetch" />Используйте метод prefetchDNS ReactDOM

Ресурсные подсказки

Элемент <link> имеет ряд ключевых слов rel, которые можно использовать для указания браузеру, что внешний ресурс, вероятно, понадобится. Браузер использует эту информацию для применения оптимизаций предзагрузки в зависимости от ключевого слова.

Хотя API метаданных напрямую не поддерживает эти подсказки, вы можете использовать новые методы ReactDOM для безопасного их добавления в <head> документа.

'use client'

import ReactDOM from 'react-dom'

export function PreloadResources() {
  ReactDOM.preload('...', { as: '...' })
  ReactDOM.preconnect('...', { crossOrigin: '...' })
  ReactDOM.prefetchDNS('...')

  return '...'
}
'use client'

import ReactDOM from 'react-dom'

export function PreloadResources() {
  ReactDOM.preload('...', { as: '...' })
  ReactDOM.preconnect('...', { crossOrigin: '...' })
  ReactDOM.prefetchDNS('...')

  return '...'
}

Начинает загрузку ресурса на раннем этапе жизненного цикла рендеринга страницы (в браузере). Документация MDN.

ReactDOM.preload(href: string, options: { as: string })
<head> output
<link rel="preload" href="..." as="..." />

Предварительно инициирует соединение с источником. Документация MDN.

ReactDOM.preconnect(href: string, options?: { crossOrigin?: string })
<head> output
<link rel="preconnect" href="..." crossorigin />

Пытается разрешить доменное имя до запроса ресурсов. Документация MDN.

ReactDOM.prefetchDNS(href: string)
<head> output
<link rel="dns-prefetch" href="..." />

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

  • В настоящее время эти методы поддерживаются только в клиентских компонентах, которые всё равно рендерятся на стороне сервера при первоначальной загрузке страницы.
  • Встроенные функции Next.js, такие как next/font, next/image и next/script, автоматически обрабатывают соответствующие ресурсные подсказки.

Поведение

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

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

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

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

Потоковая передача метаданных

Метаданные, возвращаемые generateMetadata, передаются клиенту потоком. Это позволяет Next.js встраивать метаданные в HTML, как только они разрешаются.

Поскольку метаданные страницы в первую очередь предназначены для ботов и краулеров, Next.js будет передавать метаданные для ботов, которые могут выполнять JavaScript и проверять полный DOM страницы (например, Googlebot). Однако метаданные будут блокировать рендеринг страницы для ботов с ограниченными возможностями HTML (например, Twitterbot), так как они не могут выполнять JavaScript при сканировании.

Next.js автоматически определяет пользовательский агент входящих запросов, чтобы решить, передавать ли метаданные потоком или использовать блокирующие метаданные.

Если вам нужно настроить этот список, вы можете определить его вручную, используя опцию htmlLimitedBots в next.config.js. Next.js гарантирует, что пользовательские агенты, соответствующие этому регулярному выражению, получат блокирующие метаданные при запросе вашей веб-страницы.

import type { NextConfig } from 'next'

const config: NextConfig = {
  htmlLimitedBots: /MySpecialBot|MyAnotherSpecialBot|SimpleCrawler/,
}

export default config
module.exports = {
  htmlLimitedBots: /MySpecialBot|MyAnotherSpecialBot|SimpleCrawler/,
}

Указание конфигурации htmlLimitedBots переопределит список Next.js по умолчанию, предоставляя вам полный контроль над тем, какие пользовательские агенты должны использовать это поведение. Это продвинутая настройка, и в большинстве случаев достаточно значений по умолчанию.

Порядок

Метаданные оцениваются в порядке от корневого сегмента до сегмента, ближайшего к конечному 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.

История версий

ВерсияИзменения
v15.2.0Добавлена поддержка потоковой передачи для generateMetadata.
v13.2.0viewport, themeColor и colorScheme устарели в пользу конфигурации viewport.
v13.2.0Введены metadata и generateMetadata.