руИНН
руИНН

Что видит поиск без JS, и как один фальшивый lastmod выключил переобход карты

Поисковый робот и AI-краулер видят страницу не так, как вы в браузере. Проверять это надо не на глаз, а curl-ом — и тогда выясняются неприятные вещи: например, что карта сайта неделю никому не интересна, потому что вы сами её обесценили.

Исходная позиция

руИНН — около 2950 страниц: карточки компаний, каталог, справочные материалы. Домену четыре недели. Задача понятная: попасть в индекс Яндекса и Google, а заодно быть цитируемым в ответах AI-ассистентов — для сервиса проверки контрагентов это второй канал после поиска.

Первое, что нужно понять про робота: JavaScript он выполняет неохотно и не всегда. Googlebot умеет рендерить, но ставит такие страницы в отдельную очередь и приходит к ним позже. YandexBot с JS работает заметно хуже. AI-краулеры в массе своей не выполняют его вообще — берут HTML как есть. Если контент появляется только после fetch, для половины потребителей страница пуста.

Что отдаётся в HTML

Карточка компании рендерится на сервере целиком, и в исходном HTML лежит:

Отдельная история — данные для браузера. Раньше сервер отдавал HTML, а клиент тут же запрашивал ровно те же данные вторым запросом: посетитель смотрел на спиннер, хотя всё уже приехало. Теперь данные едут вместе со страницей в теге

<script type="application/json" id="preload">. Именно JSON-тегом, а не window.DATA = {...}: такой тег браузер не исполняет, это просто текст. Побочный, но важный эффект — на странице не осталось ни одного инлайнового скрипта, и из CSP удалось убрать unsafe-inline. На проде замер: страница Сбербанка 348 КБ, по сети 47 КБ в gzip — те же байты, что раньше, но на один сетевой круг меньше.

Файлы для AI

Кроме robots.txt и sitemap.xml сайт отдаёт два файла по спецификации llmstxt.org:

Стоит это дёшево (оба собираются из того же кэша каталога, что и sitemap), а смысл в том, что модель, пришедшая за фактами, получает их в готовом виде, а не пытается разобрать вёрстку. В robots.txt при этом явно прописаны YandexBot и Mail.RU: в рунете основной AI-канал идёт через Яндекс.

Ещё один сигнал, который легко упустить, — возраст данных. На карточке стоит видимая строка «Данные ЕГРЮЛ ФНС на DD.MM.YYYY» и dateModified в разметке, обе — из времени изменения файла кэша. Без явного возраста краулер считает источник заброшенным.

И тут — фальшивый lastmod

29 июля плановая проверка индексации показала странное. В поиске Яндекса восемь страниц из 2956, это ожидаемо для молодого домена. А вот в разделе про карту сайта в Вебмастере:

last_access: 22 июля, urls_count: 80.

То есть робот не перечитывал карту неделю и считал, что в ней 80 адресов вместо 2956. Полез в код и нашёл:

const today = new Date().toISOString().split('T')[0];
// ...
xml += `<url><loc>${host}/company/${slug}</loc><lastmod>${today}</lastmod>…`;

Во все <lastmod> подставлялась текущая дата. Карта каждый день заявляла, что изменились разом все 2955 страниц.

Для поисковика это не мелочь. lastmod — сигнал доверия: по нему решают, какие страницы перечитывать в первую очередь. Карта, которая ежедневно объявляет весь сайт обновлённым, не несёт информации, и обе крупные системы в таком случае поступают одинаково — перестают учитывать lastmod вообще. Не по отдельной странице, а по всему хосту.

Починка оказалась дешёвой, потому что настоящая дата уже была под рукой: карточки берут время изменения файла кэша (оно и так лежит в записи каталога, лишних чтений диска нет), справочные страницы — время изменения своего исходника, главная и каталог — самую свежую дату по базе. После деплоя карта выглядит так, как и должна: 2942 URL с датами 22–23 июля, когда наполнялась база, шесть страниц от 28-го, несколько от 29-го.

Заодно ушёл рассинхрон: раньше карта говорила «страница изменилась сегодня», а сама страница в dateModified — «22 июля». Два сигнала свежести противоречили друг другу, и верить в такой ситуации роботу было нечему.

Чем это проверять

Кабинеты вебмастеров показывают состояние, но не отвечают на вопрос «что именно видит робот». Практический набор:

Что забирать с собой