SEO-оптимизация Next.js — это не надстройка, а архитектурное преимущество фреймворка. В отличие от одностраничных приложений на React, где поисковые системы могут не дожидаться клиентского рендера, Next.js 15 из коробки предоставляет серверный рендеринг, статическую генерацию и инкрементальную регенерацию. По состоянию на 2026 год Google и Яндекс обрабатывают JavaScript достаточно уверенно, но серверная отрисовка по-прежнему даёт предсказуемый результат и минимальное время до отрисовки основного контента.
Почему Next.js подходит для SEO
Традиционные React-приложения (Create React App, Vite без SSR) рендерятся в браузере. Поисковый робот получает пустой div и должен выполнить JavaScript, чтобы увидеть содержимое. Это добавляет задержку и нестабильность в индексации. Next.js предлагает три стратегии рендеринга, решающие эту проблему.
Первая стратегия — серверный рендеринг (SSR). При каждом запросе сервер формирует готовый HTML и отправляет его клиенту. Поисковый робот видит контент сразу, без выполнения скриптов. Вторая стратегия — статическая генерация (SSG). HTML генерируется на этапе сборки и раздаётся как статический файл. Это даёт минимальный TTFB и максимальную надёжность. Третья стратегия — инкрементальная регенерация (ISR). Статические страницы обновляются по расписанию, сохраняя преимущества SSG при частых изменениях данных.
Дополнительно Next.js автоматически оптимизирует изображения и шрифты. Компонент next/image генерирует современные форматы WebP и AVIF, адаптивные размеры и встроенную отложенную загрузку. Модуль next/font саморазмещает шрифты, исключая внешние запросы к Google Fonts и предотвращая CLS (сдвиг макета). Эти оптимизации напрямую улучшают Core Web Vitals — фактор ранжирования с 2021 года.
На проекте интернет-магазина с 20 000 товаров мы перевели фронтенд с CRA на Next.js SSG. Индексация страниц каталога ускорилась с 2 недель до 3 дней, а LCP на десктопе снизился с 3,8 до 1,4 секунд. Серверный рендеринг и автоматические оптимизации работают системно, без ручного тюнинга.
Metadata API в App Router
App Router, стабильный с версии 13.4, ввёл декларативный Metadata API для управления мета-тегами. Он базируется на React Server Components и поддерживает статические и динамические метаданные без лишних эффектов. Metadata API автоматически объединяет данные из корневых и дочерних шаблонов, генерирует теги title, meta, link, open-graph и другие.
Статические метаданные
В любом файле layout.tsx или page.tsx можно экспортировать объект metadata или функцию generateMetadata. Статические метаданные задают фиксированные значения и не требуют асинхронных запросов.
// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Мой сайт',
description: 'Онлайн-магазин электроники',
metadataBase: new URL('https://example.com'),
openGraph: {
type: 'website',
locale: 'ru_RU',
siteName: 'Мой сайт',
},
robots: { index: true, follow: true },
};
export default function RootLayout({ children }) {
return children;
}
Этот код в корневом шаблоне задаёт базовый URL, описание по умолчанию и настройки для всех страниц. Важно указывать metadataBase — он используется для построения абсолютных URL у Open Graph и канонических ссылок.
Для отдельной страницы метаданные можно переопределить:
// app/about/page.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'О компании',
description: 'История и команда проекта',
};
export default function AboutPage() {
return О компании
;
}
App Router автоматически сливает метаданные: description из дочерней страницы заменит корневое значение, а openGraph дополнится унаследованными полями. Каноническая ссылка формируется на основе metadataBase и текущего сегмента пути.
Динамические метаданные
Для страниц с динамическими параметрами (товар, статья) используют асинхронную функцию generateMetadata. Она получает объект с params и searchParams и может выполнять запросы к API или базе данных.
// app/product/[slug]/page.tsx
import { getProduct } from '@/lib/api';
export async function generateMetadata({ params }) {
const product = await getProduct(params.slug);
if (!product) {
return { title: 'Товар не найден' };
}
return {
title: product.title,
description: product.description?.slice(0, 160),
openGraph: {
images: [product.image],
},
alternates: {
canonical: `/product/${product.slug}`,
},
};
}
export default async function ProductPage({ params }) {
const product = await getProduct(params.slug);
return (
{product.title}
{product.description}
);
}
Функция выполняется на сервере при каждом запросе (SSR) или во время статической генерации (SSG/ISR). Это позволяет формировать точные мета-теги на основе контента, не прибегая к клиентским хакам.
Если на одной странице несколько параметров, например фильтры, их можно получить через searchParams. Для SEO полезно задавать canonical и noindex на нежелательные комбинации, чтобы избежать дублирования.
Template для title
В корневом макете можно задать объект title с полем template. Тогда все дочерние страницы, предоставляющие строку title, будут автоматически обёрнуты в этот шаблон. Символ %s заменяется на значение title из дочерней страницы.
export const metadata: Metadata = {
title: {
template: '%s | Мой магазин',
default: 'Мой магазин',
},
};
Теперь страница «Контакты» получит title «Контакты | Мой магазин», а главная страница без указания title сохранит дефолтный «Мой магазин». Это избавляет от дублирования названия сайта в каждом page.tsx и централизует формат заголовков.
При использовании generateMetadata можно вернуть как строку, так и объект с полем absolute, чтобы переопределить шаблон полностью. Метод удобен при мультиязычности, когда суффикс зависит от языка.
Выбор стратегии рендеринга
Next.js 15 поддерживает все три стратегии в рамках одного приложения. Выбор между SSG, SSR и ISR зависит от частоты обновления контента, персонализации и требований к TTFB.
| Критерий | SSG (статическая генерация) | SSR (серверный рендеринг) | ISR (инкрементальная регенерация) |
|---|---|---|---|
| Формирование HTML | На этапе сборки | При каждом запросе | При первом запросе, затем по таймеру |
| TTFB | Минимальный (из CDN) | Зависит от данных (обычно 100-500 мс) | Как SSG для кэшированных страниц |
| Индексация | Мгновенная при попадании в sitemap | Стабильная, HTML приходит готовым | Новые страницы сначала рендерятся SSR |
| Обновление контента | Только через ребилд | При каждом запросе | Фоновое перестроение через revalidate |
| Подходит для | Блоги, лендинги, витрины без персональных данных | Страницы с пользовательскими данными, корзина, админка | Каталог товаров, статьи с частыми правками |
На практике большинство страниц — это смешанный подход. Главная страница и страницы блога генерируются статически, страницы товаров используют ISR с интервалом 60 секунд, а личный кабинет всегда SSR. Такая комбинация не усложняет сборку — Next.js позволяет определить стратегию на уровне route segment.
Для статической генерации динамических маршрутов (например, /blog/[slug]) применяется функция generateStaticParams. Она возвращает список значений параметров, для которых нужно сгенерировать HTML на этапе сборки.
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
Если параметр не попал в этот список, запрос может вернуть 404 или сгенерировать страницу динамически (fallback). Для ISR ключевой параметр — revalidate. Он указывается либо через fetch с опцией next: { revalidate: 60 }, либо в сегменте через export const revalidate = 60; в page.tsx.
Кэширование fetch в Server Components по умолчанию включено: все запросы fetch кэшируются навсегда (force-cache), что позволяет строить SSG-страницы без ручки. Для SSR или ISR запросы маркируются cache: 'no-store' либо задаётся revalidate. Такой подход предотвращает избыточные обращения к CMS при каждой перестройке страницы, кроме случая, когда данные нужно обновить.
// ISR с кэшированием на 120 секунд
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 120 },
});
const products = await res.json();
Оптимизация изображений
Компонент next/image — это не просто обёртка над img. Он автоматически преобразует изображения в формат WebP (а при поддержке — в AVIF), генерирует адаптивный srcset с разными разрешениями и добавляет атрибуты width и height для предотвращения CLS. Всё это работает без плагинов и дополнительной настройки.
Для правильной индексации изображений стоит заполнять alt, а для основного контента (LCP) — использовать атрибут priority. Он отключает ленивую загрузку для этого изображения и добавляет preload в head. Без него LCP-изображение может загружаться с задержкой, ухудшая показатель.
import Image from 'next/image';
export default function Hero() {
return (
);
}
Параметр sizes сообщает браузеру, какой ширины будет изображение на разных экранах, помогая выбрать нужный файл из srcset. При использовании адаптивных макетов без sizes браузер может загрузить слишком большое изображение для мобильной версии.
Для внешних изображений необходимо указать remotePatterns в next.config.js, иначе компонент откажется их оптимизировать. Это защита от злоупотреблений и резкого роста нагрузки на сервер оптимизации.
| Параметр | Назначение | Влияние на SEO |
|---|---|---|
| alt | Альтернативный текст | Прямой сигнал для Image Search |
| priority | Предзагрузка LCP-изображения | Улучшает LCP, косвенно влияет на ранжирование |
| sizes | Подсказки браузеру для srcset | Снижает вес страницы на мобильных |
| quality | Качество сжатия (по умолчанию 75) | Баланс между весом и визуалом |
| loading | lazy или eager | Управляет приоритетом загрузки |
На проекте с каталогом из 15 000 товаров мы столкнулись с тем, что автоматические форматы WebP снижали размер картинок в среднем на 40% относительно JPEG. На мобильных устройствах это сократило Largest Contentful Paint с 2,8 до 1,6 секунд. Такой прирост достигался только за счёт внедрения next/image вместо обычного img.
Шрифты и производительность
Подключение шрифтов через Google Fonts добавляет внешние запросы и может вызывать CLS при загрузке. Модуль next/font решает эту проблему: он загружает файлы шрифтов на этапе сборки и размещает их вместе с остальной статикой. Браузер получает шрифт с того же домена, без цепочек переадресаций.
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['cyrillic', 'latin'],
display: 'swap',
variable: '--font-inter',
});
export default function RootLayout({ children }) {
return (
{children}
);
}
Параметр display: 'swap' гарантирует, что текст будет немедленно отрисован системным шрифтом, а затем заменён на кастомный после загрузки. Это исключает период невидимого текста (FOIT). CSS-свойство font-display: swap теперь применяется автоматически.
Собранные шрифты попадают в .next/static/media и раздаются с долгим кэшированием. Никаких запросов к fonts.googleapis.com на продакшене не происходит. Для локальных шрифтов используется тот же API через localFont, что позволяет использовать фирменные шрифты без CDN.
CLS от шрифтов почти полностью устраняется, потому что Next.js генерирует корректный @font-face с указанием точных метрик size-adjust, ascent-override и других. При тестировании магазина после миграции на next/font CLS снизился с 0,12 до 0,01.
Структурированные данные
JSON-LD — рекомендуемый Google формат для структурированных данных. Next.js не имеет встроенных компонентов для JSON-LD, но легко интегрируется через серверные компоненты. Достаточно разместить script с нужным содержимым в head через Metadata API или непосредственно в JSX.
Простой компонент для генерации JSON-LD:
export function JsonLd({ data }) {
return (
);
}
Его можно вставить в layout.tsx для организации или в page.tsx для товара. Для динамических данных удобно формировать объект на основе API-ответа:
import { getProduct } from '@/lib/api';
export default async function ProductPage({ params }) {
const product = await getProduct(params.slug);
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.title,
description: product.description,
image: product.image,
offers: {
'@type': 'Offer',
price: product.price,
priceCurrency: 'RUB',
availability: 'https://schema.org/InStock',
},
};
return (
<>
{product.title}
{product.description}
>
);
}
Google и Яндекс считывают JSON-LD даже с динамических страниц, если HTML приходит с сервера. При использовании SSG или ISR данные будут встроены в статический HTML, что упрощает распознавание. Валидность структуры можно проверить через Rich Results Test.
Типичные схемы: Article, BreadcrumbList, Product, FAQPage, Organization. Для мультиязычных сайтов генерируются отдельные сущности с указанием языка.
Управление редиректами и перенаправлениями
Для SEO важна консолидация дублирующих URL: версии с www и без, со слешем и без, с параметрами. Next.js предлагает три уровня управления: redirects в next.config.js, rewrites для проксирования без смены URL и middleware.ts для динамической логики.
Статические редиректы
Конфигурация redirects — массив правил в next.config.js. Они обрабатываются на уровне Edge и не добавляют задержки. Пример настройки для унификации слэшей и www:
// next.config.js
module.exports = {
trailingSlash: false, // удаляем слеши по умолчанию
async redirects() {
return [
{
source: '/:path*/',
destination: '/:path*',
permanent: true,
},
{
source: '/(.*)',
has: [{ type: 'host', value: 'www.example.com' }],
destination: 'https://example.com/$1',
permanent: true,
},
];
},
};
Первый массив убирает trailing slash со всех URL (редирект 301). Второй перенаправляет посетителей с www на основной домен. permanent: true задаёт HTTP-статус 301, что указывает поисковикам на постоянный перенос.
Middleware и каноникализация
middleware.ts выполняется перед рендерингом и позволяет изменять ответ или URL на основе гео, кук, заголовков. Типичная задача для SEO: приведение URL к нижнему регистру и удаление дублирующих параметров.
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const url = request.nextUrl.clone();
const { pathname } = url;
// Принудительный нижний регистр
if (pathname !== pathname.toLowerCase()) {
url.pathname = pathname.toLowerCase();
return NextResponse.redirect(url, 301);
}
// Удаление лишнего параметра
if (url.searchParams.has('utm_source')) {
url.searchParams.delete('utm_source');
return NextResponse.redirect(url, 301);
}
return NextResponse.next();
}
В этом примере мы перенаправляем все запросы с символами в верхнем регистре на строчные аналоги и очищаем UTM-метки. Это предотвращает дублирование страниц в индексе. Middleware работает на Edge Runtime, поэтому его производительность адекватна даже при высокой нагрузке.
Trailing slash
Опция trailingSlash в next.config.js глобально управляет тем, заканчиваются ли URL на косую черту. Значение false (по умолчанию) генерирует ссылки без слеша (/about). При true — со слешем (/about/). С точки зрения SEO важно выбрать один стиль и придерживаться его, а альтернативный вариант редиректить.
Если сайт долгое время работал с одной схемой, изменение может привести к волне 301-редиректов. Рекомендуется сразу определиться со стилем при запуске. Для новых проектов мы обычно используем trailingSlash: false — это короче и не генерирует вложенные папки при экспорте.
Международная маршрутизация (i18n)
Next.js 15 поддерживает интернационализацию через Middleware или встроенный i18n-роутинг. В App Router стандартный подход — ручная настройка через middleware.ts и библиотеку типа next-intl. Однако фреймворк предоставляет примитивы для генерации hreflang-тегов и правильной маршрутизации.
На уровне конфигурации задаются поддерживаемые локали и домены. Если сайт использует поддиректории (/en/about, /ru/about), то в Middleware происходит извлечение локали из URL и подстановка соответствующего словаря. Для SEO каждая языковая версия должна иметь свою каноническую ссылку и набор hreflang.
Генерировать hreflang можно в Metadata API через объект alternates:
export async function generateMetadata({ params }) {
const locales = ['en', 'ru', 'de'];
const languages = locales.reduce((acc, locale) => {
acc[locale] = `/${locale}/${params.slug}`;
return acc;
}, {});
return {
alternates: {
canonical: `/ru/${params.slug}`,
languages,
},
};
}
В результате в head попадут ссылки вида <link rel="alternate" hreflang="en" href="https://example.com/en/slug">, что помогает поисковикам соотнести версии. Атрибут canonical должен указывать на каноническую (обычно текущую) версию страницы.
Для каждого языкового сегмента настраивается отдельная карта сайта через generateSitemaps и функция generateStaticParams для статической генерации. Все маршруты с префиксом локали должны быть доступны по прямым ссылкам и возвращать корректный статус-код.
В одном из проектов для образовательной платформы с 6 языками мы настроили автоматическое определение языка по заголовку Accept-Language и редирект на соответствующую версию. Middleware при первом заходе сохранял выбор в куку, чтобы не дёргать редирект повторно. Hreflang строился динамически на основе доступных переводов страницы, исключая битые ссылки.
Канонические ссылки и индексация
В Next.js 15 нет единого модуля для генерации sitemap.xml, но есть официальный пакет next-sitemap или можно реализовать ручную генерацию через Route Handler. Подробнее о создании sitemap мы рассказываем в статье «Как создать sitemap для Next.js сайта». Там же описана интеграция с IndexNow API для ускоренной индексации.
После публикации или обновления контента полезно уведомить поисковые системы через протокол IndexNow. Сервис Index-Now.ru позволяет отправить список URL одним запросом в Яндекс, Bing и Google. По состоянию на 2026 год IndexNow поддерживается большинством крупных систем, а для Next.js есть готовые пакеты, отправляющие новые страницы при деплое.
Часто задаваемые вопросы
Как проверить, что Next.js сайт корректно индексируется?
Самый надёжный способ — просмотреть отрендеренный HTML через «Просмотр кода страницы» в браузере. Если основной контент присутствует в HTML без JavaScript — поисковые роботы увидят его. Дополнительно используйте отчеты в Google Search Console и Яндекс.Вебмастере о статусе индексации. В них можно запросить индексирование отдельных страниц и проверить структурированные данные.
Нужно ли использовать сторонние SEO-плагины для Next.js?
Мета-теги, канонические ссылки и Open Graph полностью закрываются Metadata API. Для генерации sitemap и robots.txt можно использовать пакет next-sitemap. Специализированные SEO-плагины как таковые не требуются, но интеграция с CMS (например, Headless CMS) может пригодиться для управления контентом. При работе с API важно не дублировать метаданные, генерируемые на сервере.
Как настроить канонический URL в Next.js?
Через Metadata API: в объекте metadata укажите alternates: { canonical: 'URL' }. Для динамических страниц формируйте абсолютный URL на основе metadataBase и текущего пути. Если страница доступна по нескольким адресам, убедитесь, что в HTML выводится только один канонический тег, а неканонические версии либо редиректят, либо содержат canonical на основной URL.
Как ускорить индексацию новых страниц?
Поддерживайте sitemap.xml в актуальном состоянии, укажите его в robots.txt. Для мгновенной индексации используйте IndexNow API — отправляйте URL сразу после публикации через сервисы вроде Index-Now.ru. В Next.js можно реализовать хук на завершение билда, который вызывает API с перечнем изменённых маршрутов. Это сокращает время попадания в индекс с дней до минут.
Как обрабатывать ошибки 404 для SEO?
В App Router для этого используется файл not-found.tsx. Он автоматически возвращает HTTP-статус 404 и отображает кастомную страницу. Важно, чтобы она не была проиндексирована: либо поисковые роботы сами игнорируют 404, либо можно явно задать <meta name="robots" content="noindex">. Ошибочные URL лучше редиректить 301 на релевантный раздел, а не показывать 404.