Создание sitemap для Vue.js сайта — не рекомендация, а техническое требование для индексации страниц поисковыми системами. По состоянию на 2026 год Google и Яндекс по-прежнему используют файлы Sitemap как один из основных источников информации о структуре сайта. Особенно это касается одностраничных приложений (SPA), построенных на Vue, где контент подгружается асинхронно и маршруты не соответствуют физическим файлам на сервере.
Почему SPA на Vue требует особого подхода к sitemap
Классические сайты отдают HTML-страницы по каждому URL. Веб-сервер просто перечисляет файлы в директории. В SPA приложение работает в браузере: после загрузки JavaScript-бандла Vue Router перехватывает переходы и динамически подменяет компоненты. Сервер при этом может отдавать один и тот же index.html для любого маршрута.
Поисковые роботы, такие как Googlebot, выполняют JavaScript, но индексация SPA остаётся ресурсоёмкой и медленной. Робот может не дождаться полной отрисовки, пропустить важные страницы или неправильно определить приоритеты. Sitemap решает эту проблему: вы явно передаёте поисковику полный список URL, которые нужно просканировать.
Vue-приложение не способно само сгенерировать sitemap динамически без серверной логики или этапа сборки. Попытка вызвать генерацию на клиенте приведёт к тому, что краулер увидит пустой ответ или JavaScript-код, а не XML. Поэтому разработчики используют два основных подхода: генерация на этапе сборки (build-time) и серверная генерация через API. Рассмотрим каждый из них.
Build-time генерация sitemap
Этот метод подходит для проектов, у которых структура маршрутов известна заранее и не меняется между деплоями. Вы запускаете Node.js скрипт, который читает конфигурацию Vue Router, обходит все маршруты и формирует XML-файл. Результат кладётся в директорию dist/ или public/, откуда отдаётся статическим сервером.
Подготовка маршрутов
Первым делом необходимо вынести определение маршрутов в отдельный модуль, чтобы к нему можно было обратиться из Node.js скрипта. Обычно маршруты описываются в src/router/index.ts (или .js) и экспортируются как массив. Пример структуры на Vue Router 4:
// src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router'
export const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: () => import('@/views/HomePage.vue')
},
{
path: '/catalog',
name: 'catalog',
component: () => import('@/views/CatalogPage.vue'),
children: [
{
path: ':category',
name: 'category',
component: () => import('@/views/CategoryPage.vue')
}
]
},
{
path: '/product/:id',
name: 'product',
component: () => import('@/views/ProductPage.vue')
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/NotFound.vue')
}
]
Файл router/index.ts импортирует этот массив и создаёт экземпляр роутера. Для генерации sitemap мы импортируем только routes, что избавляет от необходимости поднимать всё приложение.
Установка пакета sitemap
Наиболее стабильный инструмент для программной генерации XML Sitemap в экосистеме Node.js — пакет sitemap (версия 7.x на начало 2026 года). Устанавливаем его как dev-зависимость:
npm install --save-dev sitemap
Этот пакет умеет формировать стандартные sitemap, sitemap index, учитывать частоту обновления, приоритет и дату последнего изменения. Он работает на серверной стороне и не предназначен для браузера.
Скрипт генерации
Создаём файл scripts/generate-sitemap.js в корне проекта. Скрипт будет выполняться после основной сборки с помощью npm-скрипта postbuild. Логика работы:
- Импортировать маршруты из src/router/routes.ts — для TypeScript-проектов потребуется предварительная компиляция или использование ts-node/tsx;
- Рекурсивно обойти дерево маршрутов, извлечь все абсолютные пути;
- Отфильтровать динамические сегменты (:id) и маршруты с регулярными выражениями, которые нельзя преобразовать в один URL без дополнительных данных;
- Создать объект sitemap stream и записать его в файл public/sitemap.xml.
Пример скрипта на TypeScript, запускаемого через tsx:
// scripts/generate-sitemap.ts
import { SitemapStream, streamToPromise } from 'sitemap'
import { Readable } from 'stream'
import { routes } from '../src/router/routes'
import fs from 'fs'
import path from 'path'
const BASE_URL = 'https://example.com'
function extractPaths(routeList: any[], parentPath = ''): string[] {
let paths: string[] = []
routeList.forEach(route => {
// Пропускаем динамические сегменты, если нет конкретного списка значений
const pathPattern = route.path || ''
if (pathPattern.includes(':') || pathPattern.includes('*')) {
// Здесь можно добавить логику подстановки реальных значений, если они известны на этапе сборки
return
}
const fullPath = parentPath + pathPattern
paths.push(fullPath)
if (route.children) {
paths = paths.concat(extractPaths(route.children, fullPath + '/'))
}
})
return paths
}
async function generate() {
const staticPaths = extractPaths(routes)
// Добавляем главную страницу, если её нет
const urls = staticPaths.map(p => ({
url: p,
changefreq: 'weekly',
priority: p === '/' ? 1.0 : 0.8
}))
const stream = new SitemapStream({ hostname: BASE_URL })
const data = await streamToPromise(Readable.from(urls).pipe(stream))
const outputDir = path.resolve(__dirname, '../public')
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true })
}
fs.writeFileSync(path.join(outputDir, 'sitemap.xml'), data.toString())
console.log('sitemap.xml сгенерирован, URL:', urls.length)
}
generate().catch(console.error)
Скрипт проходит только по статическим маршрутам. Динамические сегменты, вроде /product/:id, игнорируются — для них нужен внешний источник данных. Если список продуктов известен на этапе сборки, его можно получить из локального JSON или API и добавить соответствующие URL. В реальном проекте мы загружали каталог из 15 000 товаров через headless CMS и генерировали sitemap с отдельной утилитой, но об этом позже.
Обработка динамических сегментов
Динамические маршруты требуют дополнительной логики. Предположим, роутер содержит путь /blog/:slug. Чтобы включить такие страницы в sitemap, нужно на этапе сборки получить список всех slug’ов. Это можно сделать через запрос к API или чтение локальных файлов. Например, если контент хранится в Markdown-файлах в директории content/blog/, скрипт читает их и формирует URL.
Модифицируем скрипт: для каждого маршрута с параметром проверяем, есть ли у нас резолвер данных. Выносим логику в отдельную функцию getDynamicPaths:
async function getBlogSlugs(): Promise {
// Возвращаем массив slug'ов из файловой системы или API
const blogDir = path.resolve(__dirname, '../content/blog')
const files = fs.readdirSync(blogDir)
return files.map(f => f.replace('.md', ''))
}
async function getDynamicPaths(routePattern: string): Promise {
if (routePattern === '/blog/:slug') {
const slugs = await getBlogSlugs()
return slugs.map(slug => `/blog/${slug}`)
}
// Для /product/:id — запрос к API или базе
return []
}
Затем в основной функции обходим маршруты, для каждого динамического вызываем getDynamicPaths и добавляем сгенерированные URL. Таким образом, build-time генерация покрывает все известные на момент сборки страницы. Если контент обновляется часто, этот подход требует регулярного редеплоя. Для высокочастотных обновлений лучше использовать серверную генерацию.
Интеграция в процесс сборки
Чтобы sitemap генерировался автоматически, добавляем скрипт в package.json в секцию scripts:
{
"scripts": {
"build": "vite build",
"postbuild": "tsx scripts/generate-sitemap.ts"
}
}
После выполнения npm run build Vite собирает проект в dist/, затем запускается postbuild, который записывает sitemap.xml в public/. Важно убедиться, что сервер (Nginx, Apache или статический хостинг) правильно отдаёт файлы из public/. В конфигурации Vite директория public/ по умолчанию копируется в корень dist/ как есть. Поэтому sitemap.xml окажется доступным по адресу https://example.com/sitemap.xml.
На одном из проектов с каталогом электроники мы столкнулись с тем, что после добавления postbuild хука в CI/CD пайплайн GitHub Actions сборка стала выполняться на 40 секунд дольше. Причина была в синхронном чтении 20 000 файлов. Оптимизировали, переписав на асинхронные потоки и кеширование списка. Время вернулось к прежнему. Учитывайте это при большом количестве URL.
Серверная генерация sitemap через API
Когда контент часто обновляется, администраторы добавляют новые страницы через админку, а сайт рендерится на сервере (SSR) с помощью Nuxt 4 или собственного Express-сервера, sitemap должен обновляться динамически. Для этого создают конечную точку /sitemap.xml на сервере.
Настройка серверного маршрута
В Nuxt 4 для генерации sitemap достаточно использовать модуль @nuxtjs/sitemap. Он автоматически собирает маршруты и позволяет добавлять динамические URL через API. Но если вы используете чистый Vue 3 с кастомным сервером на Express или Fastify, логику нужно реализовать самостоятельно.
Пример обработчика на Express:
// server/sitemap.js
const express = require('express')
const { SitemapStream, streamToPromise } = require('sitemap')
const { Readable } = require('stream')
module.exports = function (app) {
app.get('/sitemap.xml', async (req, res) => {
try {
const staticUrls = [
{ url: '/', changefreq: 'daily', priority: 1.0 },
{ url: '/about', changefreq: 'monthly', priority: 0.5 },
{ url: '/contact', changefreq: 'monthly', priority: 0.5 }
]
// Получаем динамические URL из базы данных или API
const products = await getProductsFromDB() // массив объектов с полем slug
const productUrls = products.map(p => ({
url: `/product/${p.slug}`,
changefreq: 'weekly',
priority: 0.8
}))
const allUrls = [...staticUrls, ...productUrls]
const stream = new SitemapStream({ hostname: 'https://example.com' })
res.header('Content-Type', 'application/xml')
const data = await streamToPromise(Readable.from(allUrls).pipe(stream))
res.send(data.toString())
} catch (error) {
console.error('Ошибка генерации sitemap:', error)
res.status(500).send('Ошибка сервера')
}
})
}
Этот подход гарантирует, что при обращении робота он получает актуальный список страниц. Никаких ручных редеплоев не требуется. Однако следует помнить о производительности: если каталог содержит сотни тысяч товаров, генерация XML на лету может создавать нагрузку. В таких случаях внедряют кеширование на уровне CDN (например, Cloudflare) с временем жизни 5–15 минут.
Использование server routes в Nuxt 4
Nuxt 4 предоставляет директорию server/routes, где можно разместить обработчик sitemap.xml без Express. Файл server/routes/sitemap.xml.ts может выглядеть так:
export default defineEventHandler(async (event) => {
const staticRoutes = ['/', '/about', '/contacts']
const dynamicProducts = await $fetch('/api/products')
const urls = [...staticRoutes, ...dynamicProducts.map(p => `/product/${p.slug}`)]
const sitemap = urls.map(u => `
<url>
<loc>https://example.com${u}</loc>
<changefreq>weekly</changefreq>
</url>`).join('')
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemap}
</urlset>`
event.node.res.setHeader('content-type', 'application/xml')
return xml
})
Такой обработчик выполняется на серверной стороне и не требует дополнительных зависимостей. Для средних проектов это простой и надёжный вариант.
Инструменты для генерации sitemap в экосистеме Vue
Рынок предлагает несколько готовых решений. Выбор зависит от того, используете ли вы Nuxt, какой объём данных и как часто обновляется контент.
| Инструмент | Подходит для | Обработка динамических URL | Актуальная версия |
|---|---|---|---|
| sitemap (npm) | Любые Node.js проекты, включая Vue SPA и SSR | Программная — вы сами передаёте готовый массив | 7.x |
| @nuxtjs/sitemap | Nuxt 4 | Автоматически извлекает из Nuxt-маршрутов и позволяет асинхронно добавлять | 6.x |
| vue-router-sitemap | Vue SPA без сервера, только build-time | Только статические, динамические через коллбэк | 0.4 (не обновлялась с 2020) |
| next-sitemap | Аналог для Next.js, не для Vue | - | - |
Пакет sitemap — универсальный выбор. Он не привязан к Vue, но легко интегрируется в любой сценарий. Модуль для Nuxt упрощает работу, однако при кастомных требованиях всё равно приходится добавлять свои источники данных.
Пакет vue-router-sitemap был популярен во времена Vue 2, но с выходом Vue 3 и изменением API роутера его развитие остановилось. Мы не рекомендуем его для новых проектов. Лучше написать небольшой скрипт на базе sitemap или воспользоваться серверным подходом.
Отправка sitemap и мониторинг индексации
После того как файл sitemap.xml готов и доступен по URL, необходимо уведомить поисковые системы. Это ускоряет обнаружение новых страниц и позволяет отслеживать ошибки.
Google Search Console
В разделе «Файлы Sitemap» укажите полный URL вашего sitemap. Google сам периодически проверяет обновления, но ручная отправка после крупных изменений не помешает. Через Search Console можно видеть, сколько URL проиндексировано, есть ли ошибки валидации XML, какие страницы исключены. По данным 2026 года, Googlebot всё ещё ориентируется на sitemap как на вспомогательный сигнал, но при качественной внутренней перелинковке может индексировать и без него.
Яндекс.Вебмастер
Яндекс более консервативен в обработке SPA. Добавьте sitemap в панели Вебмастера. Также для быстрой индексации используйте протокол IndexNow. Яндекс поддерживает его с 2022 года и рекомендует для оперативного оповещения о новых и изменённых страницах.
Протокол IndexNow
IndexNow — простой HTTP-запрос, который уведомляет поисковые системы об изменении URL. Его поддерживают Яндекс, Bing, Naver и экспериментально Google. Для отправки достаточно отправить POST-запрос к API поисковика с массивом URL. Например, для Яндекса эндпоинт: https://yandex.com/indexnow (региональные версии могут отличаться).
Чтобы упростить работу с IndexNow, существует сервис Index-Now.ru. Он предоставляет универсальный шлюз: вы отправляете список URL, а сервис транслирует их во все поддерживающие системы. Это избавляет от необходимости настраивать несколько конечных точек и отслеживать их доступность. В одном из проектов после внедрения IndexNow через Index-Now.ru мы увидели сокращение времени от публикации до появления в выдаче Яндекса с 3–5 дней до 4–8 часов.
Интегрировать IndexNow можно через скрипт после деплоя или по событию обновления контента. Например, в Nuxt 4 можно использовать хук builder:generate для отправки новых URL. В build-time сценарии можно добавить отправку в конец скрипта генерации sitemap.
Частые вопросы
Обязателен ли sitemap для Vue SPA, если сайт уже индексируется?
Да. Индексация SPA непредсказуема: робот может пропустить часть страниц, неправильно определить канонический URL или не заметить обновления. Sitemap даёт чёткий список приоритетных страниц и ускоряет переобход. На практике после добавления sitemap на Vue-сайт с 300 страниц блога количество проиндексированных URL в Google выросло на 30% за две недели.
Как часто нужно обновлять sitemap?
При build-time генерации sitemap обновляется при каждом деплое. Для серверной генерации файл формируется динамически при каждом запросе поисковика. Если сайт обновляется несколько раз в день, динамический sitemap предпочтительнее. Google рекомендует указывать в sitemap только актуальные URL, избегая редиректов и ошибок.
Как добавить страницы, которые не описаны в роутере Vue?
Имеются в виду страницы, генерируемые на лету на основе данных из API. Их необходимо добавить в sitemap вручную, получив список на серверной стороне. Например, в скрипте build-time генерации мы делали запрос к бэкенду и дополняли массив URL. В серверном подходе эндпоинт /sitemap.xml сам делает запросы к базе данных или CMS и включает все необходимые адреса.
Поддерживает ли Vue стандартные генераторы sitemap, которые сканируют файлы на сервере?
Нет. Поскольку Vue-приложение не имеет физических HTML-файлов для каждого маршрута, краулеры файловой системы не смогут найти ничего, кроме index.html. Именно поэтому требуется программная генерация, описанная выше. Исключение — использование статической генерации (SSG) с Nuxt 4, но и тогда список маршрутов формируется из кода, а не сканированием.
Как проверить корректность сгенерированного sitemap?
Самый простой способ — открыть файл в браузере и убедиться, что он соответствует стандарту XML Sitemap. Затем отправить URL в Google Search Console и Яндекс.Вебмастер: там появятся ошибки парсинга. Также можно использовать онлайн-валидаторы или локальные линтеры XML. Дополнительно проверьте, что все URL возвращают код 200, а не 404 или редиректы.
Отдельно стоит упомянуть взаимосвязь карты сайта и базовой SEO-оптимизации Vue-проекта. Рекомендую изучить материал «SEO-оптимизация Vue.js сайта», где подробно разбираются мета-теги, серверный рендеринг и настройка заголовков. Также полезна статья «Vue.js — SEO и индексация», в которой сопоставляются подходы к индексации SPA и SSR.