SEO и контент

Sitemap.xml для динамического сайта: что добавлять, что исключать и когда обновлять

На небольшом сайте Sitemap выглядит просто.

Есть десять страниц:

/
/services
/prices
/about
/contacts
/expert/article-1
/expert/article-2
...

Разработчик один раз создаёт:

/sitemap.xml

и практически забывает о нём.

На динамическом сайте эта модель перестаёт работать.

Сегодня в редакционной системе:

35 статей.

Через полгода:

300.

Появляются:

новые материалы;
рубрики;
кейсы;
страницы услуг;
архивы;
пагинация;
старые slug;
фильтры;
запланированные публикации;
удалённые страницы.

Часть URL должна индексироваться.

Часть существует только технически.

Часть перенаправляет на новый адрес.

Часть доступна пользователю, но имеет:

<meta name="robots" content="noindex">

Часть вообще находится за авторизацией.

Если просто выгрузить в Sitemap все маршруты приложения или все записи базы данных, получится файл, который начинает противоречить самому сайту.

Например:

sitemap.xml:
https://example.ru/expert/old-article

HTTP:
410 Gone

или:

sitemap.xml:
https://example.ru/search?q=crm

meta robots:
noindex

или:

sitemap.xml:
https://example.ru/expert/article?utm_source=telegram

canonical:
https://example.ru/expert/article

Во всех трёх случаях Sitemap сообщает поисковой системе:

Этот URL важен и его стоит рассматривать для поиска.

А сам сайт одновременно говорит:

Нет, не стоит.

Поэтому правильный Sitemap динамического проекта начинается не с XML.

Он начинается с определения:

какие URL мы действительно хотим видеть в поисковом индексе?


Что вообще делает Sitemap

Sitemap — это файл, который сообщает поисковой системе о значимых URL сайта и помогает их обнаруживать.

Например:

<?xml version="1.0" encoding="UTF-8"?>

<urlset
  xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
>
  <url>
    <loc>
      https://example.ru/expert/api-versioning
    </loc>

    <lastmod>
      2026-10-07
    </lastmod>
  </url>
</urlset>

Но Sitemap не является командой:

Добавить страницу в индекс.

Google прямо рекомендует включать в Sitemap URL, которые вы хотите видеть в результатах поиска, и в случае дублирующихся адресов указывать прежде всего канонические варианты. При этом наличие URL в Sitemap остаётся сигналом для crawling, а не гарантией индексирования.

Яндекс описывает Sitemap похожим образом: файл сообщает поисковику о текущей структуре сайта, но наличие URL в нём не гарантирует появление страницы в поисковой выдаче.

То есть правильная модель:

Sitemap
   ↓
поисковик узнаёт URL
   ↓
crawler может его посетить
   ↓
анализирует HTTP,
robots, canonical,
контент
   ↓
принимает решение
об индексировании

Не:

URL в Sitemap
=
URL в поиске

Sitemap — это не список всех URL сайта

Это одна из самых важных мыслей.

Представим приложение имеет:

/public
/admin
/account
/api
/search
/expert
/services

Технически все они являются URL.

Но Sitemap не должен превращаться в:

полный дамп routing table.

Например:

/admin

вообще не предназначен поисковому роботу.

/account/projects

является личным кабинетом.

/api/projects

является API.

/search?q=crm

может быть внутренней поисковой страницей.

А:

/expert/postgresql-queue

— полноценной публичной статьёй.

Следовательно, Sitemap должен описывать поисковую поверхность продукта, а не техническую поверхность приложения.


Удобнее считать Sitemap представлением данных

В динамическом продукте можно мыслить так:

Database
     ↓
Publication state
     ↓
SEO policy
     ↓
Canonical URL
     ↓
Sitemap projection

То есть Sitemap — это почти обычный database view.

Например в базе есть:

article_id
slug
status
published_at
updated_at
seo_indexable
deleted_at

В Sitemap попадают только записи, соответствующие определённым условиям.

Условно:

status = PUBLISHED

AND seo_indexable = true

AND deleted_at IS NULL

AND public_url exists

Но этого ещё недостаточно.

Финальный URL должен соответствовать реальному HTTP и canonical policy.


Какая страница должна попадать в Sitemap

Для обычной публичной SEO-страницы хорошая картина выглядит так:

URL:
https://example.ru/expert/article

HTTP:
200 OK

robots:
crawl allowed

meta robots:
index allowed

canonical:
https://example.ru/expert/article

Sitemap:
https://example.ru/expert/article

Все сигналы показывают на один документ.


Именно такие страницы являются хорошими кандидатами

Например:

главная;
страницы услуг;
цены;
публичные кейсы;
экспертные статьи;
публичные категории;
важные информационные страницы.

При условии, что бизнес действительно хочет получать на них поисковый трафик.


Главный принцип

В Sitemap стоит добавлять канонический URL страницы, которую мы действительно хотим видеть в поиске.

Google прямо рекомендует помещать в Sitemap полные абсолютные URL и при наличии нескольких адресов одного содержимого включать предпочтительный canonical.

Яндекс перед созданием Sitemap также рекомендует сначала определить канонические URL страниц.


Значит tracking URL не нужен

Есть:

https://example.ru/expert/article

и:

https://example.ru/expert/article
?utm_source=telegram

а также:

https://example.ru/expert/article
?utm_source=yandex

Если это один материал, Sitemap должен содержать:

https://example.ru/expert/article

Не три адреса.


То же относится к ref, gclid, yclid и другим служебным параметрам

Если параметр не меняет самостоятельный поисковый документ:

не включаем его вариант
как отдельный URL.

Иначе один материал может породить:

/article
/article?utm_source=yandex
/article?utm_source=telegram
/article?ref=partner

четыре строки в Sitemap.

Хотя поисковая сущность всего одна.


Sitemap должен совпадать с canonical

Представим Sitemap содержит:

/article?ref=partner

Но страница говорит:

<link
  rel="canonical"
  href="https://example.ru/article"
>

Сайт одновременно сообщает:

Sitemap:
важен /article?ref=partner

и:

Canonical:
основной /article

Лучше не создавать такой конфликт.


Поэтому можно сформулировать invariant

Для обычной индексируемой страницы:

sitemap URL
=
canonical URL
=
финальный URL после redirect
=
URL внутренних ссылок

Чем чаще выполняется это правило, тем проще поисковой системе понимать структуру сайта.


Что точно не стоит добавлять

Первая большая категория — страницы:

noindex.

Если документ возвращает:

<meta
  name="robots"
  content="noindex"
>

или:

X-Robots-Tag: noindex

нет смысла одновременно помещать его в список URL, которые мы хотим видеть в поиске.

Google прямо рекомендует не включать в Sitemap адреса, которые владелец сайта не хочет видеть в результатах поиска.


Например

/thank-you

нужна пользователю после формы.

Но поисковая landing page из неё не требуется.

Если policy:

noindex

то в Sitemap:

не включаем.

Не включаем личные кабинеты

Например:

/account
/account/projects
/account/files

Во-первых, такие страницы обычно защищены авторизацией.

Во-вторых, поисковому роботу они не представляют публичной информационной ценности.

Sitemap не должен быть списком интерфейсов приложения.


Не включаем admin

/admin
/admin/articles
/admin/projects

не имеют отношения к органическому поиску.


Не включаем API endpoints

/api/projects
/api/articles
/api/health

Sitemap описывает индексируемые ресурсы сайта, а не весь HTTP API.


Не включаем 404

Страница:

/expert/nonexistent

возвращает:

404 Not Found.

Её не должно быть в Sitemap.


Не включаем 410

Если статья удалена окончательно:

/expert/old
→ 410 Gone

она должна исчезнуть и из Sitemap.


Не включаем URL с permanent redirect

Например:

/expert/old-api
→ 301
/expert/api-versioning

В Sitemap должен находиться:

/expert/api-versioning

а не старый адрес.


Это особенно важно после изменения slug

Было:

/expert/webhook-old

Стало:

/expert/webhook-signature-replay

Правильная модель:

old URL
→ 301 new URL

new URL
→ 200

Sitemap
→ new URL only

Не нужно хранить старый URL в Sitemap «ради SEO-веса»

Для этого существует redirect.

Sitemap описывает актуальную структуру сайта, а не историю его маршрутов.


Черновики тоже не нужны

В CMS есть:

DRAFT
SCHEDULED
PUBLISHED
ARCHIVED
DELETED

Черновик:

DRAFT

не должен попадать в Sitemap только потому, что запись уже существует в базе.


То же относится к отложенной публикации

Статья запланирована:

publish_at =
2026-10-10 10:00

Сегодня:

2026-10-07.

Она ещё не является публичной страницей.

Следовательно:

Sitemap:
нет.

После фактической публикации:

Sitemap:
да.

Иначе поисковик узнаёт URL раньше пользователя

Это может создать:

404;
preview;
пустую страницу;
неожиданный noindex.

Никакой пользы здесь нет.


Динамический Sitemap должен учитывать lifecycle контента

Например:

DRAFT
    ↓
SCHEDULED
    ↓
PUBLISHED
    ↓
ARCHIVED
    ↓
DELETED

Для каждого состояния нужна понятная SEO-policy.


DRAFT
→ нет public URL
→ нет Sitemap
SCHEDULED
→ нет Sitemap
PUBLISHED + INDEXABLE
→ Sitemap
PUBLISHED + NOINDEX
→ нет Sitemap
DELETED
→ нет Sitemap
→ 404/410

Так Sitemap автоматически следует жизненному циклу продукта.


А что делать с архивными статьями

Слово:

ARCHIVED

само по себе не должно автоматически означать:

noindex.

Например старая техническая статья может по-прежнему быть полезной пользователям.

Если она:

доступна;
актуальна;
имеет самостоятельный URL;
должна участвовать в поиске,

она может остаться в Sitemap.

Архивный статус бизнеса и индексная политика — разные вещи.


Это хороший пример того, почему SEO-policy лучше хранить отдельно

Например:

content_status = ARCHIVED
seo_indexable = true

или:

content_status = ARCHIVED
seo_indexable = false

Решение становится явным.


Как быть с пагинацией

Например:

/expert
/expert?page=2
/expert?page=3

Здесь нет универсального правила:

Всегда добавлять.

или:

Никогда не добавлять.

Нужно сначала решить индексную архитектуру пагинации.


Если страницы пагинации самостоятельны и indexable

Например:

/expert?page=2

возвращает:

200;
crawl allowed;
index allowed;
self-canonical.

Тогда она может быть включена в Sitemap.


Если pagination pages имеют noindex

Тогда:

в Sitemap их быть не должно.

Главное — не создавать противоречие

Плохо:

sitemap:
?page=2

и:

?page=2:
noindex.

И не нужно автоматически склеивать всю пагинацию canonical на первую страницу

Если page 2 содержит другой набор статей, это не точная копия page 1.

Политика пагинации должна быть спроектирована отдельно.

Sitemap просто следует ей.


Что делать с фильтрами

Представим каталог:

/catalog

имеет:

?brand=apple
?color=black
?sort=price
?ram=16

Если автоматически добавить все комбинации, Sitemap быстро превратится в:

десятки тысяч технических URL.

Но некоторые фильтры могут быть полезными SEO-страницами

Например:

/catalog/laptops/apple

или даже осознанно нормализованный:

/catalog?brand=apple

имеет:

отдельный поисковый спрос;
уникальный H1;
отдельный текст;
самостоятельный набор товаров;
self-canonical.

Если бизнес действительно считает страницу поисковой landing page, её можно включить.


А сортировка — обычно другое

/catalog?sort=price

показывает те же товары в другом порядке.

Это гораздо больше похоже на техническое представление одной категории.

Обычно такой URL не нужен в Sitemap.


Значит правило снова одно

Не:

В Sitemap добавляем все URL без ?.

И не:

Все URL с ? исключаем.

А:

в Sitemap добавляем самостоятельные канонические документы, предназначенные для поиска.


Внутренний поиск обычно исключаем

Например:

/search?q=postgresql

пользователю полезен.

Но бесконечное число поисковых запросов может создавать практически бесконечное пространство URL.

Если такие страницы не являются частью SEO-стратегии:

не добавляем их в Sitemap.

Что делать с категориями

Например:

/expert/api-integrations

является полноценной страницей рубрики.

Если она имеет:

H1;
описание;
ссылки на материалы;
self-canonical;
indexable policy,

её вполне можно включить в Sitemap.


Но пустая рубрика — вопрос

Представим категория создана:

/background-jobs

но материалов:

0.

Страница показывает:

Пока нет публикаций.

Нужно ли её индексировать?

Это уже продуктовое решение.

Очень часто правильнее:

не публиковать пустую SEO-страницу

до появления содержимого.

Sitemap не должен искусственно создавать поисковую поверхность раньше контента.


То же относится к автоматически созданным тегам

CMS может создать:

/tag/api
/tag/backend
/tag/http
/tag/json
/tag/development

после первой статьи.

Это не означает, что каждый tag page автоматически заслуживает:

index + sitemap.

Иначе десятки статей быстро порождают сотни слабых архивных страниц.


Sitemap не должен становиться отражением database cardinality

Наличие сущности:

Tag

в базе не означает:

TagPage

в поиске.


Какие поля нужны XML Sitemap

Минимальный элемент:

<url>
  <loc>
    https://example.ru/expert/article
  </loc>
</url>

loc является основой.


Самое полезное дополнительное поле — lastmod

Например:

<url>
  <loc>
    https://example.ru/expert/article
  </loc>

  <lastmod>
    2026-10-07
  </lastmod>
</url>

Но только если дата правдивая.


lastmod — не дата генерации Sitemap

Это одна из самых частых ошибок.

Плохой generator:

lastmod = new Date();

Для каждой страницы при каждом построении файла.

Сегодня:

100 статей
→ lastmod = сегодня.

Завтра:

те же 100 статей
→ lastmod = завтра.

Хотя ни одна статья не изменилась.


Что мы в таком случае говорим crawler

Все сто документов изменяются каждый день.

Но это неправда.

Google прямо подчёркивает, что lastmod полезен только тогда, когда соответствует реальности; если сайт постоянно указывает ложные даты обновления, поисковая система со временем может перестать доверять такому сигналу. Google рекомендует менять lastmod при существенных изменениях основного контента, структурированных данных или значимых ссылок, но не из-за, например, ежегодного изменения copyright в footer.


Поэтому lastmod должен идти из данных страницы

Например статья имеет:

published_at
content_updated_at
seo_updated_at

Можно определить:

lastmod =
max(
  content_updated_at,
  seo_significant_updated_at
)

Но не любое обновление записи является изменением страницы

Представим admin открыл статью и система обновила:

last_viewed_by_admin

или:

internal_note_updated_at.

Публичный HTML не изменился.

Значит lastmod страницы меняться не должен.


Это ещё одна причина разделять внутренние timestamps

Плохо:

updated_at

изменяется от любого SQL UPDATE.

А Sitemap воспринимает его как:

public page modified.

Лучше иметь понятную семантику

Например:

content_updated_at

или:

public_updated_at.

Что считать значимым изменением статьи

Например:

изменён основной текст;
добавлен раздел;
исправлена важная техническая информация;
изменён H1;
обновлены структурированные данные;
значимо изменена перелинковка.

А что обычно не требует нового lastmod

Например:

поменялся copyright;
обновился счётчик просмотров;
сменился CSRF token;
изменилась внутренняя аналитика;
перестроился случайный блок рекомендаций.

Категории сложнее

Страница:

/expert

сама по себе может не иметь поля:

updated_at.

Но сегодня опубликована новая статья.

Содержимое /expert действительно изменилось.

Можно вычислять:

lastmod(category) =
max(
  category_content_updated_at,
  latest_visible_article_change
)

если это действительно соответствует rendered page.


Но не обязательно изобретать дату, которой вы не доверяете

Google прямо указывает, что lastmod можно вообще не указывать на тех страницах, для которых система не умеет достоверно определить дату существенного изменения — например на некоторых агрегирующих страницах.

Лучше:

нет lastmod

чем:

всегда сегодняшняя дата.

А что с changefreq

Протокол Sitemap позволяет указывать:

<changefreq>
  weekly
</changefreq>

Но Google сейчас прямо указывает, что changefreq вообще не используется. То же относится к priority.


У Яндекса ситуация отличается

Актуальная документация Яндекса по XML Sitemap по-прежнему описывает:

lastmod;
changefreq;
priority

как поддерживаемые элементы; для priority Яндекс описывает коэффициент относительной важности URL от 0.0 до 1.0.


Нужно ли поэтому строить сложную priority-систему

Для большинства обычных проектов я бы не делал из этого большую архитектуру.

Главное:

правильный loc;
правильный набор URL;
честный lastmod.

Это намного важнее попытки вручную назначить:

0.9
0.8
0.73

каждой статье.


Порядок URL в Sitemap тоже не является способом расставить приоритет Google

Google прямо указывает, что порядок URL внутри Sitemap для него значения не имеет.

То есть:

главную поставить первой

можно для человеческого удобства.

Но это не делает её автоматически:

SEO priority №1.

Когда обновлять Sitemap

Правильный ответ:

когда изменяется набор индексируемых URL или значимое состояние уже включённой страницы.


Опубликовали новую статью

Было:

/article-a
/article-b

Появилась:

/article-c.

После публикации Sitemap должен начать содержать:

/article-c.

Изменили существующую статью

URL остаётся тем же.

Но значимо изменён контент.

Обновляем:

lastmod.

Изменили slug

Было:

/article-old

Стало:

/article-new.

Sitemap:

remove /article-old
add /article-new

Сервер:

/article-old
→ 301 /article-new.

Удалили статью

remove URL from Sitemap

и сервер возвращает:

404/410

либо:

301

если существует настоящая замена.


Поставили noindex

Если было принято осознанное решение исключить страницу из поиска:

remove from Sitemap.

Вернули страницу в index

Когда:

noindex
→ indexable

и URL снова соответствует всей SEO-policy:

добавляем обратно.

Опубликованный материал переведён в draft

Он перестаёт быть публичной SEO-страницей.

Следовательно:

удаляем из Sitemap.

И отдельно корректно решаем HTTP lifecycle самого URL.


Нужно ли пересобирать весь XML после каждого изменения

На небольшом и среднем динамическом сайте это совершенно нормально.

Например:

500;
5000;
20 000

URL можно получить из базы и быстро сформировать заново.

Но необязательно выполнять тяжёлый SQL на каждый crawler request:

GET /sitemap.xml
↓
читать всю БД
↓
рендерить 30 000 URL

каждый раз.


Лучше отделить обновление данных от отдачи файла

Например:

Publish article
      ↓
Invalidate sitemap cache
      ↓
Generate sitemap
      ↓
Store cached XML
      ↓
GET /sitemap.xml
      ↓
fast response

Или lazy cache

content changed
↓
sitemap_revision++

При следующем request:

cache stale?
   │
   ├── no → return
   │
   └── yes → regenerate

Для Sitemap важна надёжность, а не realtime в миллисекундах

Если статья опубликована в:

12:00:00

не обязательно, чтобы Sitemap изменился:

12:00:00.001.

Но он должен обновляться предсказуемо и достаточно быстро.

Например:

сразу при публикации;

или:

фоновым job в течение нескольких минут.

Не нужно генерировать новый lastmod просто потому, что XML пересобран

Это снова важное различие.

Sitemap file generated_at

и:

page lastmod

— разные timestamps.


Нужно ли каждый раз заново отправлять Sitemap в Google

Нет.

Если Sitemap находится по постоянному адресу:

https://example.ru/sitemap.xml

его не нужно после каждого изменения удалять и добавлять заново.

Google рекомендует предоставить Sitemap через Search Console и/или ссылку в robots.txt. Старый unauthenticated sitemap ping endpoint уже отключён; Google также отдельно советует не отправлять один и тот же неизменившийся Sitemap много раз в день.


Яндекс тоже регулярно проверяет уже добавленный файл

Яндекс Вебмастер прямо указывает: при обновлении добавленного Sitemap не нужно удалять его и загружать заново — робот периодически проверяет файл. При необходимости в Вебмастере можно вручную сообщить, что файл обновился.


Значит архитектура должна иметь стабильный URL Sitemap

Например:

/sitemap.xml

или для большого сайта:

/sitemap-index.xml.

И указать его в robots.txt

Например:

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

Яндекс официально поддерживает такую директиву; Google также принимает Sitemap через robots.txt.


Что делать, когда URL становится много

Один Sitemap ограничен.

И Google, и Яндекс указывают лимит:

50 000 URL

на один файл и:

50 MB

в несжатом виде. Если лимит превышен, Sitemap нужно разбивать и при необходимости объединять через Sitemap index.


Вместо:

/sitemap.xml

можно получить:

/sitemap-index.xml

который содержит:

/sitemap-pages.xml
/sitemap-services.xml
/sitemap-expert-1.xml
/sitemap-expert-2.xml
/sitemap-portfolio.xml

Sitemap index

Пример:

<?xml version="1.0" encoding="UTF-8"?>

<sitemapindex
  xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
>
  <sitemap>
    <loc>
      https://example.ru/sitemap-pages.xml
    </loc>
  </sitemap>

  <sitemap>
    <loc>
      https://example.ru/sitemap-expert.xml
    </loc>
  </sitemap>
</sitemapindex>

Разделять стоит не только ради лимита

Даже сайт на:

5000 URL

может получить пользу от логического разделения.

Например:

sitemap-static.xml
sitemap-expert.xml
sitemap-services.xml
sitemap-portfolio.xml

Так проще диагностировать:

Почему статьи плохо индексируются?

Можно отдельно анализировать:

expert sitemap.

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


Это особенно полезно для редакционной системы

Например:

sitemap-expert.xml

становится точным отражением:

всех опубликованных
indexable статей.

А не смешивается с

услугами;
портфолио;
юридическими страницами;
каталогом.

Как разделять большой Sitemap

Хороший вариант — по типу сущности.

Например:

articles;
services;
products;
categories.

Другой вариант — по времени

Если:

миллионы публикаций,

можно иметь:

articles-2025.xml
articles-2026-01.xml
articles-2026-02.xml

Но такая схема оправдана только при реальном масштабе.

Для нескольких сотен материалов она создаст лишнюю сложность.


Не проектируйте Sitemap как Kubernetes

Если на сайте:

60 страниц,

одного:

sitemap.xml

обычно достаточно.

Архитектура Sitemap должна соответствовать размеру продукта.


Что должен возвращать сам Sitemap

У Яндекса требования прямо включают:

HTTP 200 OK

для URL Sitemap, UTF-8, максимум 50 000 ссылок и 50 MB без сжатия.


Не нужно перенаправлять /sitemap.xml без причины

Плохо:

/sitemap.xml
↓ 301
/new-sitemap.xml

если можно сразу использовать стабильный конечный адрес.


Особенно неприятная ошибка

/sitemap.xml
→ 500

после deployment.

Сайт для пользователей работает.

Но поисковый робот теряет источник структуры.


Поэтому Sitemap нужен в readiness/smoke checks

Например после deployment:

GET /sitemap.xml

ожидаем:

200;
application/xml;
valid XML;
не пустой.

Но синтаксически правильный XML ещё не означает правильный Sitemap

Можно получить идеально валидный:

<urlset>
...
</urlset>

внутри которого:

200 URL

и половина:

404.

XML validator скажет:

PASS.

SEO-система должна сказать:

FAIL.

Поэтому нужна семантическая валидация

Для каждого URL можно проверять:

HTTP status;
canonical;
robots/indexability;
domain;

Sitemap содержит:

https://example.ru/expert/article

Production test делает request.

Ожидает:

HTTP 200

и:

canonical =
https://example.ru/expert/article.

Если получил 301

Ошибка Sitemap.

Он должен содержать уже target.


Если получил 404

Ошибка Sitemap.


Если получил noindex

Конфликт SEO-policy.


Если canonical другой

Тоже конфликт.

Например:

Sitemap:
/expert/article-a

Canonical:
/expert/article-b

Нужно понять:

Почему мы просим поисковик рассматривать A, одновременно утверждая, что основной документ B?

Можно сделать автоматический Sitemap audit

Условно:

for url in sitemap:

  assert sameProductionHost(url)
  assert httpStatus(url) == 200
  assert robotsAllows(url)
  assert pageIsIndexable(url)
  assert canonical(url) == url

Это уже мощный SEO regression test.


А затем проверить обратную сторону

Не только:

Все Sitemap URL корректны?

Но и:

Все опубликованные indexable страницы присутствуют в Sitemap?

Например база содержит

237 published + indexable articles.

А Sitemap:

236.

Одна статья потерялась.


Это может произойти из-за:

неправильного slug;
ошибки категории;
null published_at;
ошибки генератора;

Поэтому полезен invariant:

INDEXABLE PUBLIC ENTITIES
=
SITEMAP ENTITIES

с учётом типов страниц и осознанных исключений.


Sitemap должен строиться из того же source of truth, что и публичный сайт

Плохая архитектура:

Public site:
читает PostgreSQL

Sitemap:
читает старый JSON-файл

Через месяц они неизбежно расходятся.


Лучше

PostgreSQL
     │
     ├── public article renderer
     │
     └── sitemap generator

Одна публикационная модель.

Одна SEO-policy.


Например функция

getPublicIndexableArticles()

может использоваться:

Экспертным центром;
Sitemap generator;
SEO audit.

Не три разных определения:

Что считается опубликованной статьёй?

Но не нужно превращать Sitemap в способ навигации

Sitemap помогает поисковику обнаруживать URL.

Он не заменяет внутренние ссылки.

Статья не должна существовать только так:

есть в sitemap.xml

но:

ни одна HTML-страница
на неё не ссылается.

У здорового сайта есть оба механизма

Internal links
+
Sitemap

Яндекс прямо отмечает, что робот узнаёт страницы через внутренние и внешние ссылки, а Sitemap особенно полезен для большого сайта, глубоко вложенных страниц или URL, которые иначе можно пропустить.

Google тоже рассматривает Sitemap как помощь обнаружению, а не замену crawlable ссылкам.


Поэтому новая статья после публикации должна появляться не только в XML

Но и:

/expert;
рубрике;
релевантной внутренней перелинковке.

Sitemap не исправляет orphan pages автоматически

Он помогает поисковой системе узнать URL.

Но внутренняя архитектура сайта всё равно сообщает, насколько документ встроен в продукт.


Что делать с главной страницей

Конечно:

/

может быть в Sitemap.

Но с lastmod нужно быть осторожнее.

Если главная автоматически показывает:

последние статьи;
последние кейсы;
счётчик проектов;

она может меняться достаточно часто.


Не ставьте lastmod = now на каждый request

Можно:

lastmod =
дата последнего значимого изменения
главной страницы

или, если точную семантику определить трудно:

lastmod вообще не указывать.

Это лучше ложного сигнала.


То же для страниц категорий

lastmod можно вычислять на основе:

изменения самой категории;
новой публикации, реально отображаемой на этой странице.

Но только если модель действительно соответствует контенту.


Нужно ли менять lastmod при исправлении одной опечатки

Зависит от существенности.

Не нужно превращать это в бухгалтерию каждого символа.

Google формулирует критерий как significant modification.


«PostgreSQLl»
→
«PostgreSQL»

в одном месте статьи — вряд ли причина строить сложный специальный механизм.


А вот

переписано 30% статьи;
добавлен новый раздел;
исправлена устаревшая архитектура;
обновлена schema;

явно является существенным изменением.


Не используйте publish date вместо lastmod навсегда

При первой публикации:

lastmod = published_at

логично.

Но после редактирования:

lastmod

должен отражать новое существенное изменение.


И не подменяйте lastmod датой просмотра

Например:

page_viewed_at

вообще не относится к изменению содержимого.


Что делать при массовом обновлении шаблона

Например обновили:

header;
footer;
цвет кнопок.

Нужно ли менять lastmod у:

5000 статей?

Обычно нет, если основное содержимое документов не изменилось значимо.


А если изменили structured data у всех статей

Например исправили:

Article JSON-LD

на всех страницах.

Google прямо относит изменение structured data к примерам существенных изменений, для которых корректный lastmod может быть обновлён.

Тогда массовое изменение дат уже имеет основание.


Нужно ли включать изображения в обычный Sitemap

Можно расширять XML информацией об изображениях, видео и локализованных версиях.

Google поддерживает соответствующие extensions, а Яндекс также позволяет передавать через Sitemap дополнительную информацию для некоторых типов содержимого.

Но обычному корпоративному или экспертному сайту не нужно усложнять файл только потому, что возможность существует.


Начните с качественной карты страниц

loc
+
достоверный lastmod

уже решают основную задачу.


Что делать с мультиязычным сайтом

Например:

/ru/article
/en/article
/de/article

Если каждая языковая страница:

публичная;
indexable;
self-canonical,

каждая является самостоятельным URL и может присутствовать в Sitemap.

Для языковых связей уже используется:

hreflang

в соответствующей реализации.

Не нужно помещать в Sitemap только русскую версию и надеяться, что остальные обязательно найдутся сами.


Но again: canonical policy должна быть правильной

Если:

/en/article

ошибочно имеет:

canonical → /ru/article,

Sitemap не должен маскировать архитектурную ошибку.

Сначала исправляется canonical.

Потом Sitemap.


Sitemap должен следовать SEO-истине сайта, а не создавать её

Это очень важная мысль.

Не нужно пытаться:

Если добавим URL в Sitemap, поисковик поймёт, что он главный.

Если сама страница говорит:

canonical → другой URL,

сначала нужно исправить страницу.


Что делать при миграции домена

Было:

old.example.ru

Стало:

example.ru.

После полноценной миграции новый Sitemap должен содержать:

новые canonical URLs

на новом host.

Не смесь:

old + new.

То же при www → без www

Выбрано:

https://example.ru

Sitemap должен содержать именно этот host.

Не:

https://www.example.ru

если он уже:

301 → example.ru.

Google требует абсолютные URL

Не:

<loc>
  /expert/article
</loc>

а:

<loc>
  https://example.ru/expert/article
</loc>

Это прямо указано в актуальных рекомендациях Google.


Production base URL должен быть конфигурацией

Например:

PUBLIC_BASE_URL=https://example.ru

И Sitemap generator использует его.


Но его обязательно нужно валидировать

Иначе deployment с:

PUBLIC_BASE_URL=http://localhost:4173

может сгенерировать:

<loc>
http://localhost:4173/expert/article
</loc>

Это идеальный случай для fail-fast

Production запускается только если:

scheme = https
host = expected production host

То же для staging

Staging вообще не должен случайно использовать production Sitemap как собственный список индексируемых URL.

Лучше иметь отдельную environment policy.


Как может выглядеть sitemap generator

Не буквально, а концептуально:

getIndexablePages()
      ↓
normalizeCanonicalUrl()
      ↓
filter:
  public
  indexable
  not deleted
  not redirected
      ↓
calculateReliableLastmod()
      ↓
sort for deterministic output
      ↓
render XML
      ↓
validate
      ↓
cache

Почему deterministic output полезен

Если содержимое не изменилось, Sitemap должен оставаться практически тем же.

Не нужно при каждой генерации:

хаотично менять порядок;
переписывать даты;
создавать новый XML,
отличающийся целиком.

Так легче:

тестировать;
diff'ить;
кэшировать;
расследовать.

Хороший sitemap generator должен уметь сказать, почему URL попал в файл

Например для статьи:

URL:
https://example.ru/expert/webhook

Source:
article #1842

Status:
PUBLISHED

Indexability:
INDEX

Canonical:
SELF

Lastmod source:
content_updated_at

Это значительно облегчает диагностику.


И желательно уметь сказать, почему URL исключён

Например:

article #1944
excluded:
status = DRAFT

или:

article #1960
excluded:
seo_indexable = false

или:

old slug
excluded:
301 redirect

Тогда Sitemap становится наблюдаемой системой

Не чёрным XML-файлом:

Почему этой статьи здесь нет?

Особенно полезно это в админ-панели

Возле статьи можно показывать:

Поиск:
INDEXABLE

Sitemap:
INCLUDED

Canonical:
SELF

Lastmod:
07.10.2026

Или:

Поиск:
NOINDEX

Sitemap:
EXCLUDED

Теперь редактор понимает состояние публикации

Не только:

Опубликовано.

Но и:

Опубликовано для пользователей
и доступно поисковым системам.

Нужно ли вручную добавлять Sitemap после каждой статьи в Яндекс Вебмастер

Нет.

Если URL файла не изменился, Sitemap просто обновляется.

Яндекс периодически проверяет уже добавленный файл; удалять и загружать его заново после каждого изменения не требуется.


То же концептуально относится к Google

Стабильный Sitemap подаётся один раз через:

Search Console;
robots.txt.

После этого поисковая система возвращается к нему.


И это ещё один аргумент за динамический постоянный endpoint

Например:

/sitemap.xml

а не:

/sitemap-2026-10-07-final-v2.xml.

Когда IndexNow или переобход полезны

Механизмы ускоренного уведомления поисковых систем могут дополнять Sitemap.

Но они не заменяют его архитектурную функцию.

Sitemap отвечает:

Какие канонические публичные URL составляют сайт?

Уведомление об обновлении отвечает:

Вот конкретный URL, который недавно изменился.

Это разные задачи.


Даже если после публикации есть автоматическая отправка URL поисковой системе

Sitemap всё равно должен обновиться.

Иначе:

notification:
новая статья существует

а:

Sitemap:
её нет.

Сигналы снова расходятся.


Когда Sitemap становится плохим

Не только когда XML содержит syntax error.

Плохой Sitemap может быть технически абсолютно валидным.

Например:

20% → 301
10% → 404
15% → noindex
30% → canonical на другой URL

XML validator:

PASS.

SEO architecture:

FAIL.

Поэтому качество Sitemap измеряется согласованностью

Например:

URLs total:
1000

200 OK:
1000

self-canonical:
1000

indexable:
1000

redirects:
0

404/410:
0

Такой отчёт намного полезнее:

XML valid = yes.

Можно даже считать sitemap health

Например:

Healthy URLs:
99.8%

Canonical conflicts:
2

HTTP errors:
0

Noindex conflicts:
0

Missing indexable pages:
3

И делать release gate

Если:

canonical conflicts > 0

или:

Sitemap URL → 404

релиз не считается полностью успешным.


Особенно полезно тестировать изменения редакционной системы

Например новая версия CMS изменила:

правила slug.

Вместо ожидаемых:

/expert/article

generator начал создавать:

/expert//article.

Один автоматический Sitemap audit обнаружит дефект сразу на сотнях материалов.


Или изменился status mapping

Было:

PUBLISHED
→ Sitemap.

После рефакторинга enum:

published

больше не совпадает со старой проверкой.

Sitemap внезапно:

0 URLs.

Поэтому размер Sitemap тоже стоит мониторить

Например:

вчера:
237 expert URLs

сегодня:
12.

Это почти наверняка не естественное изменение контента.


Или наоборот

вчера:
237

сегодня:
184 000.

Возможно generator внезапно начал включать:

search;
filters;
parameters.

Очень полезен release anomaly detection

Например:

URL count change > 20%
→ warning

если deployment не предполагал массовое изменение страниц.


Это особенно важно при SEO-генераторах

Система может случайно создать:

100 000 landing pages

за один deploy.

Sitemap станет первым местом, где масштаб проблемы хорошо виден.


Нельзя автоматически считать больше URL = лучше SEO

Например:

100 качественных страниц

не становятся слабее только потому, что Sitemap маленький.

И:

100 000 шаблонных страниц

не получают ценность от присутствия в XML.


Sitemap помогает поисковой системе обнаружить страницу

Но не создаёт причину её индексировать.

Эту причину создаёт сам документ.


Правильная архитектура динамического Sitemap

Если собрать всё вместе:

                   Database
                      │
                      ▼
               Content lifecycle
                      │
                      ▼
                  SEO policy
                      │
           ┌──────────┴──────────┐
           │                     │
           ▼                     ▼
     Public renderer       Sitemap generator
           │                     │
           ▼                     ▼
 HTTP / canonical         canonical URLs
 robots / content          + honest lastmod
           │                     │
           └──────────┬──────────┘
                      ▼
                 Validation
                      │
                      ▼
                sitemap.xml
                      │
             ┌────────┴────────┐
             ▼                 ▼
          Google            Yandex

Обе ветви должны исходить из одной истины.


Практический checklist динамического Sitemap

Перед production полезно проверять:

  • в Sitemap находятся только публичные URL, предназначенные для поиска;
  • каждый URL абсолютный и использует production HTTPS-host;
  • каждый URL возвращает 200 OK;
  • Sitemap не содержит 301, 302, 404 или 410;
  • Sitemap не содержит noindex страниц;
  • каждый обычный индексируемый URL совпадает со своим canonical;
  • tracking и служебные параметры не создают отдельные записи;
  • старые slug удаляются из Sitemap после настройки redirect;
  • draft и scheduled материалы появляются только после реальной публикации;
  • удалённые материалы исчезают из Sitemap;
  • lastmod отражает существенное изменение страницы, а не время генерации XML;
  • Sitemap обновляется автоматически при изменении lifecycle контента;
  • внутренний поиск, admin, API и личные кабинеты не попадают в публичный Sitemap;
  • фильтры и pagination включаются только согласно явной SEO-policy;
  • количество URL контролируется после deployment;
  • XML валиден, доступен crawler и возвращает 200;
  • при превышении 50 000 URL или 50 MB используется несколько Sitemap и Sitemap index;
  • опубликованные indexable страницы не теряются из Sitemap;
  • Sitemap указан в robots.txt и подключён в инструментах поисковых систем.

Самый полезный вопрос при генерации каждой строки

Не:

Такой URL существует?

А:

Хотим ли мы, чтобы именно этот канонический URL рассматривался поисковой системой как самостоятельная публичная страница?

Если ответ:

да,

он хороший кандидат.

Если ответ:

нет;
не знаю;
это технический URL;
это дубль;
это redirect;
это noindex,

он, скорее всего, не должен находиться в Sitemap.


Второй вопрос

Если crawler прямо сейчас откроет URL из Sitemap, подтвердит ли сама страница обещание Sitemap?

То есть получит:

200;
indexable;
self-canonical;
полезный контент.

Если нет — нужно исправлять конфликт.


Третий вопрос

Если статья изменится или исчезнет завтра, обновится ли Sitemap автоматически?

Если процесс:

редактор публикует статью
↓
потом разработчик вручную правит sitemap.xml,

он рано или поздно сломается.


Для динамического продукта Sitemap тоже должен быть динамическим

Но не в смысле:

каждый request генерирует
случайно новый XML.

А в смысле:

он автоматически отражает реальное актуальное состояние публикационной системы.


Вместо вывода

Sitemap.xml часто воспринимают как файл:

со всеми страницами сайта.

Для динамического веб-продукта это слишком грубое определение.

Правильнее:

Sitemap — это машинное представление той части публичного сайта, которую владелец считает канонической и предназначенной для поискового обнаружения.

Именно поэтому хороший Sitemap не содержит всё подряд.

Он содержит:

актуальные;
публичные;
индексируемые;
канонические
URL.

И исключает:

draft;
scheduled до публикации;
noindex;
личные кабинеты;
admin;
API;
технический поиск;
tracking-дубли;
redirects;
404;
410;
устаревшие slug.

При изменении содержимого Sitemap должен изменяться вместе с ним:

Publish
→ добавить URL

Significant update
→ обновить lastmod

Slug changed
→ удалить old,
добавить new,
old → 301

Noindex
→ убрать URL

Delete
→ убрать URL
и вернуть 404/410

А lastmod должен отвечать не на вопрос:

Когда мы последний раз генерировали XML?

а:

Когда содержимое этой конкретной публичной страницы действительно существенно изменилось?

На больших сайтах Sitemap разбивается на несколько файлов и объединяется индексом; и Google, и Яндекс устанавливают лимит одного Sitemap в 50 000 URL и 50 MB без сжатия.

Но даже идеально сформированный XML остаётся только одним звеном.

Поисковику всё равно нужны:

нормальные внутренние ссылки;
правильный HTTP;
robots;
canonical;
доступный контент;
самостоятельная ценность страницы.

Поэтому сильная SEO-архитектура выглядит не так:

URL существует
→ добавить в Sitemap.

А так:

URL существует
      ↓
Он публичный?
      ↓
Он предназначен для поиска?
      ↓
Он возвращает 200?
      ↓
Он indexable?
      ↓
Он canonical?
      ↓
Это именно основной URL?
      ↓
Да
      ↓
Sitemap

И когда эта логика является частью кода и редакционной системы, Sitemap перестаёт быть файлом, который разработчик вспоминает проверить после публикации.

Он становится автоматическим индексным реестром сайта, всегда синхронизированным с реальным состоянием продукта.

Есть похожая задача?

Опишите продукт, интеграции и ограничения. До разработки зафиксируем объём, риски и критерии приёмки.

Обсудить проект →