SEO в Next.js: метаданные, sitemap и robots в App Router
Практика3 минРедакция Innovialabs
Коротко
В App Router SEO настраивается кодом — объект metadata или функция generateMetadata для заголовков, canonical и hreflang, файлы sitemap.ts и robots.ts для сканирования, opengraph-image для превью; главное — абсолютные URL через metadataBase и свой canonical у каждой страницы.
Как устроено SEO в App Router
В App Router не нужно вручную писать теги в <head>. Next.js собирает их из нескольких источников:
metadata— статический объект вlayout.tsxилиpage.tsx;generateMetadata— функция для динамических страниц;- специальные файлы
sitemap.ts,robots.ts,opengraph-image.tsxв папкеapp.
Метаданные наследуются: страница получает поля из всех layout выше и перезаписывает их своими. Это удобно, но становится источником ошибок, о которых ниже.
Базовые метаданные в layout
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
title: { default: 'Компания', template: '%s — Компания' },
description: 'Короткое описание сайта',
}
metadataBase превращает относительные пути (/blog/post) в абсолютные URL в canonical, hreflang и Open Graph. Без него ссылки в превью соцсетей могут сломаться.
generateMetadata для динамических страниц
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return {
title: post.title,
description: post.description,
alternates: {
canonical: `/blog/${slug}`,
languages: {
ru: `/ru/blog/${slug}`,
en: `/en/blog/${slug}`,
'x-default': `/en/blog/${slug}`,
},
},
openGraph: { type: 'article', title: post.title },
}
}
В свежих версиях Next.js params — это Promise, поэтому его нужно await. Запросы fetch с одинаковыми параметрами в generateMetadata и в самой странице дедуплицируются, лишней нагрузки нет. Для несуществующего slug вызывайте notFound(), чтобы сервер вернул 404, а не пустую страницу с кодом 200.
sitemap.ts и robots.ts
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts()
return posts.map((p) => ({
url: `https://example.com/ru/blog/${p.slug}`,
lastModified: p.updatedAt,
alternates: {
languages: { en: `https://example.com/en/blog/${p.slug}` },
},
}))
}
// app/robots.ts
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: '*', allow: '/', disallow: '/admin/' },
sitemap: 'https://example.com/sitemap.xml',
}
}
В sitemap кладите только канонические URL с кодом 200. Если адресов больше лимита одного файла (50 000), разбейте карту через generateSitemaps.
Open Graph изображения
Файл opengraph-image.tsx (или .png/.jpg) рядом со страницей автоматически добавляет og:image. Через ImageResponse из next/og картинку можно генерировать из заголовка статьи. Рекомендуемый размер — 1200×630.
Типичные ошибки, из-за которых страницы не индексируются
- Canonical в корневом layout.
alternates.canonical: '/'наследуется всеми страницами, и все они указывают на главную. Задавайте canonical на уровне каждой страницы. - Нет
metadataBase— относительные canonical и og:image собираются неверно. robots: { index: false }остался со staging или задан в общем layout.- hreflang без взаимности: русская версия ссылается на английскую, а английская — нет. Каждая версия должна перечислять все остальные и себя.
- В sitemap редиректы, 404 и неканонические адреса — робот тратит бюджет и меньше доверяет карте.
- Пустой HTML. Если контент подгружается только на клиенте через
'use client'иuseEffect, робот может увидеть пустую страницу. Рендерьте основной текст на сервере. - Мягкие 404: страница «не найдено» отдаёт код 200 вместо
notFound(). - Разнобой со слешем:
/blogи/blog/доступны оба без редиректа. - Тонкие и дублирующие страницы: тысячи почти одинаковых страниц Google часто сканирует, но не индексирует.
Как проверить результат
- Откройте исходный код страницы и найдите
<title>,canonical,hreflang,og:image. - Проверьте
/sitemap.xmlи/robots.txtв браузере. - В Search Console используйте проверку URL: она покажет, какой canonical выбрал Google и видит ли он контент.