Как создать sitemap для Nuxt.js сайта

Правильная карта сайта (sitemap.xml) для Nuxt.js сайтов, как создать и обновлять для быстрой индексации.

Карта сайта в формате XML остаётся фундаментальным инструментом для управления индексацией. В экосистеме Nuxt задача генерации sitemap решается штатным модулем, который автоматически собирает маршруты из файловой системы и умеет подтягивать динамические URL из внешних источников. По состоянию на 2026 год актуальна ветка Nuxt 3 (версии 3.12 и выше) и готовящийся к стабильному релизу Nuxt 4. Модуль @nuxtjs/sitemap развивается параллельно и поддерживает обе мажорные версии фреймворка. Ниже — практическое руководство, основанное на реальных проектах: интернет-магазине с 40 000 товаров, корпоративном блоге на трёх языках и SaaS-платформе с SSR.

Модуль @nuxtjs/sitemap: установка и базовая конфигурация

Модуль входит в официальный набор Nuxt Modules и не требует написания собственных генераторов. Он создаёт один или несколько XML-файлов, валидных по стандарту Sitemap Protocol 0.9. Установка выполняется одной командой, после чего достаточно минимальной конфигурации в nuxt.config.ts, чтобы на выходе получить рабочую карту сайта.

Установка

Добавление модуля стандартно для экосистемы Nuxt. Если проект инициализирован через nuxi, достаточно запустить:

npx nuxi module add sitemap

При ручном подходе установите пакет через менеджер зависимостей и зарегистрируйте его в конфигурации:

npm install @nuxtjs/sitemap

После этого в файле nuxt.config.ts добавляем модуль в массив modules:

export default defineNuxtConfig({
  modules: [
    '@nuxtjs/sitemap',
  ],
})

На этом этапе модуль уже готов к работе, но со стандартными настройками он будет генерировать sitemap только для статических маршрутов, обнаруженных в директории pages/. Для полноценного использования потребуется дополнительная конфигурация.

Базовая конфигурация

Все опции передаются через объект sitemap в корне конфигурации. Минимальный рабочий пример для мультиязычного блога может выглядеть так:

export default defineNuxtConfig({
  modules: ['@nuxtjs/sitemap'],
  site: {
    url: 'https://example.com',
  },
  sitemap: {
    cacheMaxAgeSeconds: 3600, // час кэширования на этапе генерации
    exclude: [
      '/admin/**',
      '/user/**',
      '/404'
    ],
    defaults: {
      changefreq: 'weekly',
      priority: 0.7,
      lastmod: new Date().toISOString()
    },
  },
})

Параметр site.url обязателен — он определяет базовый домен, от которого строятся абсолютные URL в sitemap. Если забудете его указать, модуль выдаст предупреждение при сборке. Значение cacheMaxAgeSeconds управляет тем, как долго сгенерированный XML будет считаться актуальным на сервере Nitro. При SSR-режиме это снижает нагрузку при каждом запросе /sitemap.xml. Секция exclude принимает массив glob-паттернов для исключения маршрутов, которые не должны попасть в индекс. Параметры по умолчанию в defaults применяются ко всем URL, если конкретный маршрут не переопределил их.

После сборки или запуска dev-сервера карта сайта становится доступна по адресу https://example.com/sitemap.xml. На проекте с 15 000 товаров мы использовали подобную конфигурацию как отправную точку, но вскоре обнаружили, что без динамических маршрутов и разбиения на файлы sitemap превышал лимит в 50 000 URL. Об этом — в следующих разделах.

Автоматическое обнаружение страниц из pages/

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

Как модуль сканирует pages/

В основе работы лежит интеграция с Nuxt Router. Модуль обращается к внутреннему списку маршрутов, который фреймворк строит из файлов директории pages/. Каждый маршрут, кроме динамических сегментов без заданного источника, автоматически получает запись в sitemap. Например, при такой структуре:

pages/
├── index.vue
├── about.vue
├── blog/
│   ├── index.vue
│   └── [slug].vue
└── contact.vue

В sitemap попадут следующие URL: /, /about, /blog, /contact. Маршрут /blog/[slug] требует дополнительной конфигурации через sources, так как модуль не знает, какие именно значения подставлять в параметр slug.

Для каждого статического маршрута можно переопределить частоту обновления и приоритет через meta-свойства страницы. В Nuxt 3 это делается через композабл definePageMeta или через роутерные правила в конфиге. Пример с высоким приоритетом для главной:

// pages/index.vue
definePageMeta({
  sitemap: { priority: 1.0, changefreq: 'daily' },
})

Модуль подхватывает эти настройки и применяет к соответствующей записи в XML. Это удобно для точечного управления важностью страниц без правки конфигурации модуля.

Исключение лишнего контента с помощью exclude

Автоматическое обнаружение полезно, но часто в pages/ оказываются служебные или технические страницы: личные кабинеты, формы смены пароля, страницы ошибок. Чтобы они не индексировались, используют массив exclude глобально или локально через кастомный фильтр. В одном проекте на Nuxt 3 нам потребовалось исключить все маршруты, начинающиеся с /dashboard, и ещё несколько конкретных путей. Мы применили следующий подход в nuxt.config.ts:

sitemap: {
  exclude: [
    '/dashboard/**',
    '/profile/settings',
    '/error',
  ],
  // альтернативно: функция для более тонкого контроля
  // exclude: (route) => route.path.startsWith('/test')
}

Функциональный вариант позволяет гибко фильтровать маршруты по любым признакам, включая параметры запроса или наличие определённых мета-данных. Рекомендую использовать именно функцию, если логика исключения нетривиальна — синтаксис glob здесь не сработает для проверки свойств объекта маршрута.

Динамические маршруты

Статические страницы — лишь малая часть современных сайтов. Каталоги товаров, статьи блога, профили пользователей генерируются на лету из базы данных или API. Модуль @nuxtjs/sitemap умеет получать такие URL через асинхронные источники и вставлять их в итоговый XML.

Конфигурация sources для API-эндпоинтов

Опция sources принимает массив функций (или промисов), которые должны вернуть массив объектов маршрутов. Каждый объект описывает относительный путь и дополнительные свойства sitemap. Простейший пример с обращением к внешнему REST API:

export default defineNuxtConfig({
  sitemap: {
    sources: [
      async () => {
        const response = await fetch('https://api.example.com/posts?limit=5000');
        const posts = await response.json();
        return posts.map(post => ({
          url: `/blog/${post.slug}`,
          lastmod: post.updated_at,
          changefreq: 'weekly',
          priority: 0.8,
        }));
      }
    ]
  }
})

Этот код при сборке или при ответе на запрос sitemap выполнит HTTP-запрос, получит список постов и смапит их в URL с динамическим сегментом. Важно, чтобы функция возвращала промис — модуль дожидается его разрешения перед финальной генерацией XML. На проекте с 200 000 объявлений мы использовали пагинированные запросы внутри такой функции, чтобы не нагружать API и не создавать гигантский массив в памяти за раз. При этом общее количество URL не должно превышать 50 000 в одном файле, иначе потребуется sitemap index.

Пример: динамические страницы товаров из базы данных

Если данные лежат в базе, доступной через ORM, логика помещается прямо в конфигурацию. Допустим, используется Prisma с Nuxt 3:

sources: [
  async () => {
    const { prisma } = useRuntimeConfig(); // или прямой импорт
    const products = await prisma.product.findMany({
      where: { status: 'active' },
      select: { slug: true, updatedAt: true },
      take: 45000,
    });
    return products.map(p => ({
      url: `/catalog/${p.slug}`,
      lastmod: p.updatedAt.toISOString(),
      changefreq: 'daily',
      priority: 0.9,
    }));
  }
]

При старте Nuxt приложения Nitro выполняет эти функции и кэширует результат согласно cacheMaxAgeSeconds. Когда магазин обновил 300 товаров за час, новые данные появляются в sitemap только после истечения кэша или ручного сброса. Для оперативного обновления мы использовали событийную систему Nitro (разобрана ниже), чтобы инвалидировать кэш через API.

Мульти-sitemap и Sitemap Index

Стандарт Sitemap Protocol ограничивает размер одного файла 50 000 URL и 50 МБ (несжатого). Когда сайт перешагивает эту отметку, необходимо разбить карту на несколько файлов и создать Sitemap Index. Модуль делает это прозрачно для разработчика при правильной конфигурации.

Когда нужен sitemap index

Практика показывает, что порог в 50 000 URL достигается быстрее, чем кажется. Если интернет-магазин имеет 50 000 товаров, прибавьте к этому категории, фильтры (осторожно), страницы блога, информационные разделы — и получится 52-55 тысяч. Рекомендую включать мульти-sitemap, начиная с 45 000, чтобы иметь запас. Индексный файл /sitemap_index.xml перечисляет ссылки на дочерние карты, а поисковые роботы равномерно обходят их.

Настройка разбиения на файлы

Модуль предлагает два пути: ручное указание составных частей через массив sitemaps или автоматическое разбиение на основе лимита (доступно с версии 3.x). Автоматический вариант предпочтительнее:

export default defineNuxtConfig({
  sitemap: {
    autoLastmod: true,
    sitemaps: true, // включает автоматическое создание индекса при превышении 50k
    // или вручную задать лимит:
    // sitemapLimit: 10000, // разбивать каждые 10k URL
    cacheMaxAgeSeconds: 600,
    exclude: ['/admin/**'],
    sources: [ /*...*/ ]
  }
})

Когда количество URL превышает 50 000, модуль создаёт /sitemap_index.xml и несколько /sitemap-1.xml, /sitemap-2.xml и т.д. Ссылки в robots.txt будут указывать на индекс, а не на конкретные файлы. Для явного управления составом используют массив sitemaps с объектами, описывающими каждый под-sitemap:

sitemaps: [
  {
    path: 'static-sitemap',
    sources: [], // только статические из pages
  },
  {
    path: 'blog-sitemap',
    sources: [ /* функция для статей */ ],
  }
]

Такой подход пригодился при миграции крупного портала: мы временно разделили новости и каталог, чтобы мониторить индексацию каждого сегмента отдельно через Search Console.

Интернационализация и hreflang с @nuxtjs/i18n

Сайты на нескольких языках должны сигнализировать поисковикам о региональных версиях страниц через атрибут hreflang. Модуль @nuxtjs/sitemap интегрируется с официальным решением @nuxtjs/i18n, автоматически добавляя ссылки на альтернативные локализации в каждую запись sitemap.

Интеграция с модулем i18n

Убедитесь, что в проекте установлен и настроен @nuxtjs/i18n (версия 8.x для Nuxt 3). Когда оба модуля активны, sitemap для каждого маршрута генерирует блок с перечислением доступных языков. Важно, чтобы конфигурация i18n содержала список локалей и стратегию маршрутизации (префиксная или доменная).

export default defineNuxtConfig({
  modules: ['@nuxtjs/i18n', '@nuxtjs/sitemap'],
  i18n: {
    locales: ['en', 'ru', 'de'],
    defaultLocale: 'en',
    strategy: 'prefix_except_default',
  },
  sitemap: {
    // остальные настройки
  }
})

При такой конфигурации страница /blog/nuxt-seo получит в sitemap-записи ссылки на /ru/blog/nuxt-seo и /de/blog/nuxt-seo. Атрибут hreflang формируется по языковому коду из массива locales.

Пример для мультиязычного каталога

На практике мы столкнулись с тем, что некоторые товары не переведены на все языки. Нужно исключать недоступные локализации из sitemap, чтобы избежать 404 или редиректов. Модуль позволяет управлять этим через функцию alternatives внутри конфигурации маршрута или глобально. В нашем случае мы использовали кастомный источник, который сам отвечал за локали:

sources: [
  async () => {
    const products = await $fetch('/api/products');
    return products.flatMap(product => {
      return product.availableLocales.map(locale => ({
        url: `/${locale}/catalog/${product.slug}`,
        lang: locale,
        // автоматически добавятся альтернативы, если i18n активен
      }));
    });
  }
]

Модуль самостоятельно сопоставит URL с текущими локалями и построит корректную карту с hreflang. Это упростило поддержку: раньше мы поддерживали отдельные sitemap для каждого языка, теперь всё делается автоматически.

Тонкая настройка производительности и кэширования

Генерация sitemap для крупного проекта — операция не мгновенная. Приходится дёргать внешние сервисы, ходить в базу. Правильное кэширование и обработка событий Nitro позволяют сохранить отзывчивость сайта и актуальность карты одновременно.

cacheMaxAgeSeconds и стратегии кэширования

Параметр cacheMaxAgeSeconds задаёт время жизни кэша в секундах для сгенерированного XML. По умолчанию он равен 0, то есть sitemap пересобирается при каждом запросе. Для сайта с 30 000 товаров мы установили значение 1800 (30 минут). Запросы к /sitemap.xml стали обрабатываться на порядок быстрее, а пиковая нагрузка на API товаров снизилась в 10 раз. Однако при срочном обновлении цены или появлении новой акционной страницы приходилось ждать до 30 минут, пока поисковик увидит изменения. Для решения мы добавили механизм инвалидации через Server API.

Рекомендую выбирать cacheMaxAgeSeconds исходя из частоты обновления контента. Для новостного сайта, где статьи публикуются каждый час, подойдёт 300-600 секунд. Для корпоративного сайта с редкими правками можно смело ставить 86400 (сутки).

Динамические данные через события Nitro

В ситуации, когда URL-ы формируются не только из статических файлов и sources, но и зависят от внешних событий (например, создание новой сущности через админку), удобно использовать события сервера Nitro. Модуль предоставляет доступ к composable useNitroApp(), через который можно добавлять маршруты в рантайме или инициировать сброс кэша sitemap.

Пример серверного обработчика, который добавляет экстренный URL после публикации важной новости:

// server/api/create-news.ts
export default defineEventHandler(async (event) => {
  // логика создания новости
  const news = await createNews(await readBody(event));
  // уведомляем модуль sitemap о необходимости инвалидации
  const sitemap = useNitroApp().sitemap;
  await sitemap.addRoute([
    { url: `/news/${news.slug}`, changefreq: 'hourly', priority: 0.9 },
  ]);
  // можно также полностью очистить кэш
  await sitemap.clearCache();
  return { success: true };
});

Этот подход мы внедрили на SaaS-платформе, где клиенты создавали посадочные страницы через конструктор. После сохранения страница сразу появлялась в sitemap без ожидания плановой пересборки. Для работы с внутренними методами потребовалось типизированное обращение, в Nuxt 3.x API может немного отличаться, но концепция остаётся: Nitro предоставляет хуки для динамического управления картой сайта.

Проверка и отправка sitemap в поисковые системы

Создать sitemap — половина дела. Без валидации и уведомления поисковиков файл останется незамеченным или приведёт к ошибкам индексации. Ниже — проверенный пайплайн.

Валидация XML

Перед тем как отдавать карту сайта роботам, убедитесь, что она соответствует протоколу. Проще всего открыть /sitemap.xml в браузере — многие поисковики и инструменты покажут структуру с отступами. Ошибки синтаксиса можно отловить через онлайн-валидаторы или локально с помощью линтера. На практике мы добавляем проверку в CI-пайплайн: после сборки проекта скрипт забирает /sitemap.xml и прогоняет через xmllint.

curl -s https://staging.example.com/sitemap.xml | xmllint --noout -

Такой простой шаг отловил однажды дублирующиеся URL из-за конфликта статических и динамических sources, который не ломал вёрстку, но мог вызвать предупреждения в Search Console.

Отправка в Яндекс.Вебмастер и Google Search Console

Базовая практика — указать адрес sitemap в robots.txt и добавить в панелях вебмастеров. В Nuxt мы настраиваем редирект или прямую выдачу файла, поэтому путь к нему статичен. В файле public/robots.txt или через серверный middleware добавляем строку:

Sitemap: https://example.com/sitemap.xml

В Google Search Console достаточно раз добавить URL карты сайта в разделе "Sitemaps", и система будет периодически пересчитывать количество проиндексированных страниц. Яндекс.Вебмастер также поддерживает ручную отправку, но с 2022 года оба поисковика (а также Bing, Naver и экспериментально Google) принимают уведомления через протокол IndexNow.

Для мгновенной индексации новых и обновлённых страниц мы используем сервис Index-Now.ru. Он отправляет запросы через IndexNow API сразу в несколько поисковых систем, экономя время на реализацию под каждую отдельно. После публикации контента мы вызываем их API из серверного хука Nuxt, передавая массив свежих URL. Это не заменяет sitemap, а дополняет его: карта сайта обеспечивает полный охват, а IndexNow — оперативное уведомление о критичных страницах. Подробнее о настройке SEO-окружения Nuxt читайте в статьях Nuxt.js — SEO и индексация и SEO-оптимизация Nuxt.js сайта, где разбираем серверный рендеринг, мета-теги и Core Web Vitals в контексте Nuxt.

Часто задаваемые вопросы

Как исключить отдельную страницу из sitemap, не удаляя её из pages/?

Можно использовать свойство sitemap: false в definePageMeta конкретной страницы. Другой способ — добавить путь в глобальный массив exclude в конфигурации модуля через строку или glob-шаблон.

Модуль генерирует sitemap при каждом запросе — это нормально?

Да, по умолчанию cacheMaxAgeSeconds равен 0, что приводит к сборке на каждый запрос. Для боевого окружения установите разумное время кэширования (от 300 секунд) через этот параметр. При использовании статической генерации (nuxi generate) sitemap создаётся один раз и не меняется до следующего билда.

Как добавить в sitemap страницы, которые не являются частью pages/?

Используйте опцию sources. Она принимает массив функций, возвращающих список URL. Также можно через useNitroApp() программно добавить маршруты в рантайме, например, из внешнего API или после действий пользователя.

Поддерживает ли @nuxtjs/sitemap изображения и видео в sitemap?

Базовая версия модуля фокусируется на стандартных URL-записях. Расширения для изображений () и видео требуют либо кастомного плагина, либо ручного формирования дочернего sitemap через sitemaps. В коммерческих проектах мы обходились генерацией таких расширений отдельным скриптом.

Можно ли использовать @nuxtjs/sitemap с Nuxt 2?

Модуль совместим только с Nuxt 3 и выше. Для проектов на Nuxt 2 остаётся пакет @nuxtjs/sitemap версии 2.x, который по-прежнему поддерживается, но не получает новых функций вроде автоматического Sitemap Index.