Метаданные
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
, макетах, страницах и серверных компонентах. Reactcache
можно использовать, еслиfetch
недоступен.- Next.js будет ждать завершения получения данных внутри
generateMetadata
перед потоковой передачей интерфейса клиенту. Это гарантирует, что первая часть потокового ответа включает теги<head>
.
Файловые метаданные
Для метаданных доступны следующие специальные файлы:
- favicon.ico, apple-icon.jpg и icon.jpg
- opengraph-image.jpg и twitter-image.jpg
- robots.txt
- sitemap.xml
Их можно использовать для статических метаданных или генерировать программно.
Примеры реализации можно найти в справочниках 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
. Например:
app/layout.tsx
(Корневой макет)app/blog/layout.tsx
(Вложенный макет блога)app/blog/[slug]/page.tsx
(Страница блога)
Объединение
Согласно порядку обработки, объекты метаданных, экспортированные из нескольких сегментов одного маршрута, поверхностно объединяются для формирования итоговых метаданных маршрута. Дублирующиеся ключи заменяются в соответствии с порядком.
Это означает, что метаданные с вложенными полями, такими как openGraph
и robots
, определенные в более раннем сегменте, перезаписываются последним сегментом, который их определяет.
Переопределение полей
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme is a...',
},
}
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
.
Если вы хотите сохранить некоторые вложенные поля между сегментами, переопределяя другие, можно вынести их в отдельную переменную:
export const openGraphImage = { images: ['http://...'] }
import { openGraphImage } from './shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: 'Home',
},
}
import { openGraphImage } from '../shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: 'About',
},
}
В этом примере OG-изображение используется совместно в app/layout.js
и app/about/page.js
, тогда как заголовки различаются.
Наследование полей
export const metadata = {
title: 'Acme',
openGraph: {
title: 'Acme',
description: 'Acme is a...',
},
}
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
:
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.',
}