Sitemap и robots в Next.js 15: индексация лендинга — KEL IT
Сайты 8 мин чтения

Sitemap и robots в Next.js 15: индексация SEO-лендинга без ручного XML

Лендинг собран на Next.js 15, Core Web Vitals в зелёной зоне, JSON-LD и Open Graph на месте — а в Google Search Console через две недели «Обнаружено — не проиндексировано» для половины URL. Частая причина не в контенте, а в том, что поисковик не знает полный список страниц или тратит crawl budget на /api, preview-деплои и дубли с query-параметрами.

Ручной public/sitemap.xml устаревает после каждого поста в блоге или кейса из Sanity. Статический robots.txt с Disallow: / на staging случайно попадает в production. В App Router Next.js 15 эти файлы генерируются типобезопасно через app/sitemap.ts и app/robots.ts — они участвуют в build, подхватывают динамические маршруты и деплоятся на Vercel Edge вместе с остальным приложением.

В статье — production-подход для SEO-лендинга и мультиязычного сайта: robots с разделением окружений, sitemap из CMS и файловой системы, hreflang в sitemap, лимиты Google и проверка в Search Console.

Зачем sitemap и robots лендингу в 2026 году

Google по-прежнему использует sitemap как подсказку приоритетов и свежести, а robots.txt — как правила обхода на уровне хоста. Ни один файл не гарантирует индексацию, но их отсутствие или ошибки создают предсказуемые проблемы.

Симптом в Search ConsoleЧастая техническая причина
Новые URL не появляются неделямиНет sitemap, нет внутренних ссылок
Индексируются /api/*, /_next/*robots не закрывает служебные пути
Staging попал в индексrobots на preview без noindex + без Basic Auth
Дубли ?utm= и /page/ vs /pagecanonical есть, но краулер всё равно обходит мусор

Для лендинга услуг с 5–15 статическими страницами и блогом на 20–50 статей динамический sitemap окупается с первого релиза: один источник правды, lastModified из CMS, changeFrequency по типу контента.

Robots на Next.js 15 — не замена <meta name="robots">. Страница «спасибо за заявку» или черновик в CMS должны иметь noindex в metadata, а robots лишь сужает обход тяжёлых или бесполезных путей.

robots.ts: окружения, служебные пути и ссылка на sitemap

Файл app/robots.ts экспортирует функцию, возвращающую объект MetadataRoute.Robots. Next.js отдаёт его по адресу /robots.txt с корректным Content-Type.

Базовый шаблон для production

// app/robots.ts
import type { MetadataRoute } from 'next';

const BASE = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://example.com';

export default function robots(): MetadataRoute.Robots {
  const isProduction = process.env.VERCEL_ENV === 'production';

  if (!isProduction) {
    return {
      rules: { userAgent: '*', disallow: '/' },
      sitemap: undefined,
    };
  }

  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/api/', '/admin/', '/_next/', '/private/'],
      },
    ],
    sitemap: `${BASE}/sitemap.xml`,
    host: BASE.replace(/^https?:\/\//, ''),
  };
}

Ключевые решения:

  • VERCEL_ENV !== 'production' — полный Disallow: / на preview и local. Дополнительно на preview-деплоях в Vercel включайте Deployment Protection, иначе robots не спасёт от прямых ссылок.
  • disallow для /api/ — Route Handlers и webhooks не должны попадать в индекс; краулер всё равно может найти их по ссылкам, поэтому для чувствительных endpoint добавляйте 401 или отсутствие HTML-ответа.
  • sitemap с абсолютным URL — Google требует полный URL в директиве Sitemap:.

Когда нужны отдельные правила для ботов

Для обычного лендинга одного блока userAgent: '*' достаточно. Отдельные правила для Googlebot имеют смысл, если вы сознательно разрешаете краулинг AI-ботам или наоборот закрываете их через Disallow — помните, что это не юридическая защита контента, только управление нагрузкой.

Если нужна подобная разработка — напишите в Telegram.

sitemap.ts: статика, блог и headless CMS

Файл app/sitemap.ts генерирует /sitemap.xml. Для крупных сайтов (>50 000 URL) используйте sitemap index и несколько файлов через generateSitemaps (Next.js 15); для лендинга + блога хватает одного массива записей.

Статические страницы и приоритеты

// app/sitemap.ts
import type { MetadataRoute } from 'next';

const BASE = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://example.com';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const staticRoutes: MetadataRoute.Sitemap = [
    { url: `${BASE}/`, lastModified: new Date(), changeFrequency: 'weekly', priority: 1 },
    { url: `${BASE}/services`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.9 },
    { url: `${BASE}/contacts`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.7 },
  ];

  const posts = await getBlogPosts(); // см. ниже
  const postEntries: MetadataRoute.Sitemap = posts.map((post) => ({
    url: `${BASE}/blog/${post.slug}`,
    lastModified: post.updatedAt,
    changeFrequency: 'weekly' as const,
    priority: 0.6,
  }));

  return [...staticRoutes, ...postEntries];
}

Поле priority Google официально игнорирует в пользу внутренней логики, но lastModified полезен при частых обновлениях кейсов и цен. Берите дату из CMS (_updatedAt в Sanity), не new Date() на каждый build — иначе краулер получает ложный сигнал «всё изменилось».

Подключение Sanity / Contentful без лишних запросов

Запрос к CMS в sitemap() выполняется на build time (SSG) или при revalidation — в зависимости от export const revalidate. Для лендинга с on-demand revalidation из статьи про Sanity CMS синхронизируйте:

export const revalidate = 3600; // 1 час — sitemap обновляется вместе со страницами

Кешируйте список slug в unstable_cache или на уровне fetch с next: { tags: ['blog'] }, чтобы sitemap не дергал CMS на каждый запрос /sitemap.xml в dev.

Локальные файлы блога (Content Collections / MDX)

Если статьи лежат в src/content/blog/ru/*.md, обходите файловую систему на build:

import fs from 'node:fs/promises';
import path from 'node:path';
import matter from 'gray-matter';

async function getBlogPosts() {
  const dir = path.join(process.cwd(), 'src/content/blog/ru');
  const files = await fs.readdir(dir);
  return Promise.all(
    files
      .filter((f) => f.endsWith('.md'))
      .map(async (file) => {
        const raw = await fs.readFile(path.join(dir, file), 'utf8');
        const { data } = matter(raw);
        return {
          slug: file.replace(/\.md$/, ''),
          updatedAt: new Date(data.publishedAt ?? Date.now()),
        };
      }),
  );
}

Так sitemap всегда совпадает с реальным контентом репозитория — без ручного XML после каждого merge.

Мультиязычность: alternates и отдельные sitemap

Для пары локалей ru / en каждая языковая версия URL должна быть связана через hreflang. В Next.js 15 Metadata API это делается в layout.tsx через alternates.languages; в sitemap — поле alternates:

{
  url: `${BASE}/ru/services`,
  lastModified: new Date('2026-05-01'),
  alternates: {
    languages: {
      ru: `${BASE}/ru/services`,
      en: `${BASE}/en/services`,
      'x-default': `${BASE}/en/services`,
    },
  },
}

Практика для лендинга в 2026:

  • Один домен, префиксы /ru и /en — проще для CWV и одного sitemap.
  • Отдельные домены example.ru / example.comдва sitemap и перекрёстные alternates обязательны; ошибка в x-default — частая причина «неправильной» выдачи в Search Console → Международное таргетирование.

Не добавляйте в sitemap URL с noindex (страница благодарности, черновики, /search). Robots их не удалит из индекса, если на них уже есть ссылки.

Деплой на Vercel, лимиты и Search Console

Лимиты Google Sitemap

  • До 50 000 URL и 50 MB несжатого XML на файл.
  • При превышении — sitemap index и несколько sitemap/0.xml, sitemap/1.xml через generateSitemaps в Next.js.

Для типичного SEO-лендинга с блогом вы далеки от лимита; важнее корректность URL (без trailing slash дублей — выберите один стиль в next.config trailingSlash).

Проверка после релиза

  1. Откройте https://your-domain.com/robots.txt и https://your-domain.com/sitemap.xml — статус 200, валидный XML.
  2. Search Console → Файлы Sitemap → добавьте URL sitemap → статус «Успешно».
  3. Проверка URL для 2–3 свежих страниц из sitemap — «URL есть в Google».
  4. Отчёт Страницы → исключения «Заблокировано robots.txt» — не должно включать /, /services, статьи блога.

Edge и кеширование

sitemap.xml и robots.txt по умолчанию статичны на build. Если sitemap полностью динамический (тысячи товаров), задайте export const revalidate или CDN-cache с коротким TTL — иначе Vercel отдаёт свежий sitemap только после redeploy.

Подключите уведомление в Slack/Telegram при падении build: битый sitemap.ts (throw в CMS fetch) иногда ломает весь /sitemap.xml, а не одну запись.

Типичные ошибки на Next.js 15 лендингах

ОшибкаПоследствие
NEXT_PUBLIC_SITE_URL не задан на VercelВ sitemap URL https://example.com или localhost
Дубли с и без / в концеДва URL в индексе, размытый сигнал canonical
Sitemap включает ?ref= и UTMМусорные URL в отчётах GSC
Только robots, без sitemap на новом доменеМедленное обнаружение глубоких страниц блога
allow: / + noindex на страницеНорма: robots разрешает, meta запрещает индекс
Забыли исключить OG-image routeРедко, но /opengraph-image не нужен в sitemap

Связка с уже настроенным SEO-стеком: canonical в generateMetadata, JSON-LD для Organization, Open Graph для превью — sitemap дополняет, не заменяет их. После внедрения PPR и edge deploy проверьте, что sitemap генерируется на build с теми же slug, что видит пользователь в HTML.

Нужна помощь? Telegram → или vic.kell@ya.ru

FAQ

Нужен ли sitemap, если страниц меньше десяти?

Да, как страховка для блога и новых лендингов под кампании. Google находит и маленькие сайты по ссылкам, но sitemap ускоряет появление новых URL в отчётах и упрощает диагностику.

Можно ли оставить public/sitemap.xml вместо sitemap.ts?

Можно, но при CMS и i18n файл устареет. sitemap.ts версионируется в Git, проходит code review и не требует ручного экспорта из плагинов.

Блокирует ли Disallow в robots индексацию страницы?

Нет полностью: страница может быть проиндексирована, если на неё ссылаются с других сайтов, но контент Google может не качать. Для запрета индекса используйте robots: { index: false } в metadata.

Сколько sitemap указывать в robots.txt?

Обычно один — главный sitemap.xml. При sitemap index в robots перечисляют index-файл, дочерние подтянутся автоматически.

Нужно ли отправлять sitemap в Яндекс?

Для RU-трафика — да, в Яндекс.Вебмастер отдельно. Формат тот же; hreflang Яндекс тоже учитывает.

Влияет ли sitemap на Core Web Vitals?

Нет напрямую. Косвенно — если в sitemap попали тяжёные preview-URL, краулер нагружает origin; держите sitemap чистым.

Заключение

sitemap.ts и robots.ts в Next.js 15 App Router — минимальный, но обязательный слой технического SEO для лендинга и блога. Robots отсекает preview и служебные пути, sitemap сообщает Google актуальный список URL с правильными lastModified и hreflang.

В связке с headless CMS, edge deploy на Vercel и revalidation вы получаете индексацию без ручного XML после каждого релиза. Потратьте час на настройку и проверку в Search Console — это дешевле, чем недели ожидания, пока новые кейсы и статьи появятся в выдаче сами.

KEL IT

Нужна разработка под ключ?

Я занимаюсь такими проектами профессионально. Telegram-боты, Mini Apps, сайты, мобильные и десктопные приложения. Расскажите о задаче — разберём и предложим решение.