Перейти к контенту
Neaptidestudio
блог

Чек-лист SEO для Next.js App Router: SSR-видимость, canonical, hreflang и ИИ-краулеры

Neaptide · 30 октября 2026 г. · 8 мин чтения

Проверяем, что Next.js App Router реально отдаёт в исходном HTML: видимость <h1> без opacity:0, наследование alternates, ссылки переключателя языков и редиректы.

В этой статье
Чек-лист SEO для Next.js App Router — 1 SSR HTML (<h1> виден сразу) · 2 Alternates (6 hreflang + RSS) · 3 Ссылки (<a hreflang>) · 4 Schema (JSON-LD + sitemap).

Сайт на Next.js App Router может безупречно выглядеть в браузере, но при этом скрывать главный заголовок <h1> за начальным стилем анимации opacity:0, терять теги <link rel="alternate"> при переопределении метаданных в дочерних маршрутах или прятать ссылки на языковые версии внутри несмонтированного клиентского меню. Перед релизом проверяйте сырой HTTP-ответ и серверный HTML, а не только дерево элементов после гидратации в DevTools.

Хотя Server Components по умолчанию рендерят разметку на сервере, клиентские обёртки анимаций, функция generateMetadata() на уровне страниц и логика в middleware.ts напрямую влияют на то, что получают Googlebot, Яндекс и краулеры ИИ-поиска (GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot). Ниже — пять реальных технических проблем, которые мы нашли и устранили на собственном мультиязычном сайте Neaptide на Next.js 15, и пошаговый протокол проверки.

Что показал аудит нашего собственного сайта на Next.js 15?

В октябре 2026 года мы проверили серверный HTML, HTTP-заголовки, метаданные и карту сайта Neaptide на шести языках. В таблице собраны пять обнаруженных дефектов рендеринга и маршрутизации и результаты после выкатки исправлений.

Пять технических проверок Next.js App Router по результатам аудита Neaptide
Проверенный узелПроблема в исходном SSR-ответеВнедрённая правка и результат
Обёртки анимаций Reveal.tsx и Hero.tsxЗаголовок <h1>, лид и карточки отдавались в SSR со стилем style="opacity:0;transform:translateY(12px)" до гидратации (~1,2 с задержки)Убрали opacity:0 из начального SSR-состояния; заголовки и текст сразу видимы в исходном HTML
Хелпер generateMetadata() в lib/meta.tsПоле alternates: { canonical, languages } на страницах затирало alternates из layout и удаляло <link rel="alternate" type="application/rss+xml">Добавили types: { "application/rss+xml": "https://www.neaptide.ai/feed.xml" } в общий хелпер pageMetadata()
Переключатель языков components/LangSwitcher.tsxЯзыковые версии рендерились только после клика как <button> без атрибута hrefВсе 6 локалей рендерятся в SSR DOM как ссылки <a href={...} hrefLang={l} lang={l}>
Редирект корневого маршрута / в middleware.tsПри запросе / ботами без гео-заголовков и Accept-Language не отдавался полный заголовок VaryДобавили детерминированный переход на x-default (/en) и Vary: Accept-Language, Cookie, x-vercel-ip-country, cf-ipcountry
Микроразметка lib/schema.ts и app/sitemap.tsНа трёх страницах услуг в JSON-LD был зашитый areaServed: United States; в sitemap не входили иллюстрации кейсов и статейВыровняли areaServed и добавили локализованные обложки и графики доказательств в sitemap.xml

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

Почему style="opacity:0" в анимациях появления вредит LCP и краулерам?

На многих сайтах на Next.js первый экран, заголовки и карточки оборачивают в клиентские компоненты плавного появления или Framer Motion с начальным состоянием opacity: 0. Поскольку Next.js предварительно рендерит Client Components на сервере в их стартовом состоянии, в исходном HTML главный заголовок <h1> и первый абзац приходят с инлайновым стилем style="opacity:0".

Это создаёт сразу две проблемы. Во-первых, браузер не засчитывает заголовок или лид-абзац как отрисованный элемент Largest Contentful Paint (LCP), пока не загрузится и не выполнится клиентский JavaScript — на мобильной сети это легко добавляет 800–1200 мс. Во-вторых, поисковые роботы и краулеры ИИ-систем, читающие первичный HTML без выполнения скриптов, видят основной контент страницы явно помеченным как невидимый.

Оставляйте текст полностью видимым в SSR-разметке с первого байта: используйте мягкий сдвиг transform: translateY(12px) без opacity: 0 либо включайте прозрачность только после инициализации клиентского скрипта с учётом prefers-reduced-motion.

Как generateMetadata может незаметно удалить теги alternates из layout?

В Next.js App Router объекты метаданных объединяются поверхностно (shallow merge). Если в корневом или языковом layout.tsx задан блок alternates (например, ссылка на RSS-ленту), а дочерняя страница возвращает в generateMetadata() собственный объект alternates с полями canonical и languages, дочерний объект полностью заменяет родительский alternates.

Чтобы ни один маршрут не терял x-default, языковые альтернативы или RSS-фид, формируйте весь объект alternates в единой функции-хелпере:

export function pageMetadata(locale: Locale, opts: PageMetaInput): Metadata {
  const canonicalUrl = `${SITE.url}${localeHref(locale, opts.path)}`;
  const languages: Record<string, string> = {};
  for (const l of LOCALES) {
    languages[l] = `${SITE.url}${localeHref(l, opts.path)}`;
  }
  languages["x-default"] = `${SITE.url}${localeHref(X_DEFAULT_LOCALE, opts.path)}`;

  return {
    alternates: {
      canonical: canonicalUrl,
      languages,
      types: {
        "application/rss+xml": `${SITE.url}/feed.xml`,
      },
    },
  };
}

Почему переключатель языков и корневой редирект должны быть явными для роботов?

Если список языков в шапке монтируется в DOM только после клика по кнопке или переключает локаль через router.push() на элементе <button>, поисковый робот не находит в HTML ни одной прямой ссылки на другие языковые ветки сайта. Даже при наличии <link rel="alternate" hreflang="..."> в <head> рендеринг обычных ссылок <a href="/en/..." hrefLang="en" lang="en"> в DOM шапки ускоряет переобход всех локалей.

Отдельно проверьте поведение корневого маршрута / при запросе без cookie, гео-заголовков и Accept-Language. Middleware должен стабильно перенаправлять таких роботов на локаль по умолчанию (x-default, например /en) и отдавать заголовок Vary: Accept-Language, Cookie, x-vercel-ip-country, cf-ipcountry, чтобы CDN не закешировал случайный гео-редирект для поискового бота.

Как проверить релиз сайта на Next.js App Router за шесть шагов

Перед выкаткой изменений прогоните шесть быстрых проверок на тестовом или боевом домене:

  1. Видимость контента в сыром SSR HTML: запросите страницу через curl -s и убедитесь, что единственный <h1> и основной текст присутствуют в HTML без style="opacity:0" и атрибута hidden.
  2. Полнота canonical и hreflang: проверьте, что каждая языковая страница содержит самоссылающийся <link rel="canonical">, все активные локали, x-default и ссылку на RSS.
  3. Краулинговые ссылки в навигации и переключателе языков: убедитесь, что меню, футер и переключатель языков отрендерены как теги <a href="...">.
  4. Маршрутизация корня и адресов без префикса языка: проверьте / и старые URL через curl -I с пустым Accept-Language — должен возвращаться один постоянный переход (308 или 301) на целевую страницу 200 OK.
  5. Согласованность JSON-LD с контентом страницы: сверьте поля WebPage, Service, Article, BreadcrumbList и FAQPage с видимым текстом, языком, датами и географией услуги.
  6. Покрытие в sitemap.xml и llms.txt: убедитесь, что все коммерческие страницы, статьи и их изображения включены в /sitemap.xml, а /llms.txt актуален для ИИ-поисковиков.
curl -s https://www.neaptide.ai/en | grep -E '<h1|rel="canonical"|hrefLang|application/rss\+xml'
curl -I -H 'Accept-Language:' https://www.neaptide.ai/

Если вам нужен инженерный разбор сайта или веб-приложения на Next.js, мы проведём технический SEO- и GEO-аудит с воспроизводимыми проверками и готовыми задачами для разработчиков.

частые вопросы

Коротко о главном

Гарантирует ли использование Server Components в Next.js отсутствие проблем с SEO?

Нет. Server Components рендерят разметку на сервере, но клиентские обёртки анимаций с opacity:0, неполные объекты в generateMetadata(), кнопки вместо ссылок <a href> и редиректы в middleware могут скрывать контент или ломать каноникалы и hreflang.

Почему style="opacity:0" в SSR-разметке вреден, если скрипт всё равно показывает текст?

Начальный opacity:0 откладывает отрисовку крупного контента (LCP) до завершения гидратации JavaScript и помечает заголовки как невидимые для краулеров и ИИ-ботов, анализирующих сырой HTML.

Почему на внутренних страницах пропадает ссылка на RSS из корневого layout.tsx?

Next.js объединяет метаданные поверхностно: когда дочерняя страница возвращает собственный объект alternates с canonical и languages, он полностью заменяет родительский alternates, если в общий хелпер не включено поле types.

Нужны ли ссылки <a hreflang> в переключателе языков, если hreflang уже прописан в <head>?

Да. Прямые ссылки <a href="..." hrefLang="..."> в шапке позволяют поисковым роботам переходить между языковыми версиями прямо по DOM-дереву на каждой странице.

Куда должен вести корневой маршрут / при запросе от поискового робота без заголовка языка?

На каноническую версию по умолчанию (x-default, например /en) с заголовком Vary: Accept-Language, Cookie, x-vercel-ip-country, cf-ipcountry, чтобы кеш CDN не смешивал ответы для разных регионов.

Что входит в технический SEO-аудит Next.js от Neaptide?

Мы проверяем сырой SSR HTML, метрики Core Web Vitals, матрицу canonical и hreflang, микроразметку JSON-LD, карту сайта и доступность для ИИ-краулеров, передавая список задач с привязкой к конкретным файлам проекта.