Чтобы сказать Яндексу «страница изменилась, приди посмотреть», аккаунт в Вебмастере не нужен. Есть протокол IndexNow: вы кладёте в корень сайта файл с ключом, шлёте POST со списком адресов — и поисковик приходит за часы вместо недель. Ни регистрации, ни OAuth, ни капчи. Реализуется тридцатью строками на Node.js без единой зависимости.
Ниже — рабочий скрипт целиком, шесть кодов ответа, которые он может получить, и три грабли, на которые я наступил, пока доводил его до состояния «запустил и забыл». Одна из них интереснее прочих: fetch в Node падает с fetch failed на том же запросе, который curl отправляет с первого раза.
Что такое IndexNow и кто его поддерживает
Протокол придумали Microsoft и Яндекс в 2021 году. Идея простая: вместо того чтобы робот ходил по сайту и гадал, что изменилось, сайт сам сообщает об этом. Уведомление, отправленное на общий шлюз api.indexnow.org, раздаётся всем участникам протокола сразу.
Поисковик |
Поддержка IndexNow |
|---|---|
Яндекс |
да, плюс собственная точка |
Bing |
да |
Seznam (Чехия) |
да |
Naver (Корея) |
да |
нет, только Search Console |
Про Google важно сказать сразу, чтобы не было разочарования: он в протоколе не участвует и участвовать не собирается. Если ваша аудитория в Google — IndexNow не отменяет Search Console, а дополняет её со стороны Яндекса и Bing.
Как подтверждаются права на сайт
Никак не через аккаунт. Вы придумываете ключ — строку из 8–128 символов, буквы и цифры — и кладёте в корень сайта файл <ключ>.txt, внутри которого лежит ровно этот же ключ и больше ничего. В запросе вы передаёте и сам ключ, и адрес файла. Поисковик скачивает файл, сравнивает содержимое с ключом из запроса и, если совпало, принимает список.
https://example.com/f20093d1e2d3bdb9a72f908e260a7020.txt
Ключ не секретный: он лежит в открытом доступе по условиям протокола. Смысл не в тайне, а в доказательстве, что у отправителя есть доступ к корню сайта.
Отсюда правило, которое стоит записать в README проекта: этот файл нельзя удалять при чистке репозитория. Пропал файл — IndexNow начинает отвечать 403, причём молча, без объяснений. Мой скрипт поэтому проверяет наличие файла до отправки и падает с понятным текстом, а не отправляет запрос в пустоту.
Как выглядит рабочий скрипт
Никаких зависимостей: fetch есть в Node начиная с 18-й версии, парсинг sitemap делается регуляркой, потому что XML тут ровно из одного тега.
Главное решение в этом скрипте — не держать список адресов отдельно. Он берётся из sitemap.xml, который всё равно ведётся. Два списка страниц неизбежно разъедутся, и разъедутся молча.
const fs = require('fs'); const path = require('path'); const КЛЮЧ = 'f20093d1e2d3bdb9a72f908e260a7020'; const ХОСТ = 'bindus.ru'; const КОРЕНЬ = path.join(__dirname, '..'); // Без файла ключа в корне IndexNow ответит 403 — проверяем до отправки. const файлКлюча = path.join(КОРЕНЬ, `${КЛЮЧ}.txt`); if (!fs.existsSync(файлКлюча)) { console.error(`Нет файла ключа ${КЛЮЧ}.txt в корне сайта.`); process.exit(1); } const карта = fs.readFileSync(path.join(КОРЕНЬ, 'sitemap.xml'), 'utf8'); const адреса = [...карта.matchAll(/<loc>\s*([^<\s]+)\s*<\/loc>/g)].map(m => m[1]); const тело = JSON.stringify({ host: ХОСТ, key: КЛЮЧ, keyLocation: `https://${ХОСТ}/${КЛЮЧ}.txt`, urlList: адреса }); const точки = [ ['общий шлюз', 'https://api.indexnow.org/IndexNow'], ['Яндекс напрямую', 'https://yandex.com/indexnow'] ]; (async () => { for (const [имя, адрес] of точки) { try { const ответ = await fetch(адрес, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: тело }); const текст = await ответ.text(); const итог = ответ.status === 200 || ответ.status === 202 ? 'принято' : 'ОШИБКА'; console.log(`${имя}: ${ответ.status} ${ответ.statusText} — ${итог}`); if (текст.trim()) console.log(текст.trim().slice(0, 500)); } catch (e) { console.log(`${имя}: не достучались — ${e.message}`); } } })();
Запускать только после деплоя. Это не формальность: поисковик придёт за ключом и за страницами почти сразу, и если на проде их ещё нет, вы получите 403 на ключ или обход старой версии страниц.
Что означают коды ответа
За один запрос протокол разрешает до 10 000 адресов, так что дробить список почти никогда не нужно.
Код |
Что значит |
Что делать |
|---|---|---|
|
приняли, ключ проверен |
ничего |
|
приняли, ключ ещё проверяется |
ничего, это нормально при первой отправке |
|
тело запроса не разобралось |
смотреть JSON, чаще всего кодировка или лишняя запятая |
|
ключ не совпал |
файл в корне отсутствует, недоступен или содержит не то |
|
адреса не с этого хоста |
|
|
слишком часто |
не слать один и тот же список по кругу |
Отдельно про 202: его легко принять за проблему, потому что в привычной логике «принято» — это 200. Здесь 202 значит «список взяли, ключ проверим асинхронно». У меня первая отправка 21 адреса дала 202 от обоих шлюзов, и это был правильный ответ.
Почему fetch обрывается там, где curl проходит
Теперь то, ради чего этот текст, вероятно, и стоит читать.
24 августа скрипт написал:
общий шлюз: не достучались — fetch failed Яндекс напрямую: 202 Accepted — принято
Дважды подряд. Первая мысль была очевидная и неправильная: сломался ключ или адреса. Проверять её не пришлось — второй шлюз в том же запуске принял тот же самый список с тем же самым ключом. Тело запроса, значит, в порядке.
Второй кандидат — шлюз лежит. Проверяется одной командой:
curl -X POST -H "Content-Type: application/json; charset=utf-8" \ -d '{"host":"bindus.ru","key":"f20093d1e2d3bdb9a72f908e260a7020","keyLocation":"https://bindus.ru/f20093d1e2d3bdb9a72f908e260a7020.txt","urlList":["https://bindus.ru/"]}' \ https://api.indexnow.org/IndexNow
Прошло с первого раза. Шлюз жив.
Остаётся третье: обрыв соединения на стороне клиента. fetch failed в Node — это обёртка undici над сетевой ошибкой, и в неё заворачивается всё подряд: таймаут TLS-рукопожатия, обрыв keep-alive, отказ резолвера, разрыв соединения посредником. Сообщение при этом одинаковое, а причина в e.cause, куда никто не смотрит, потому что в примерах из документации её не печатают.
Практический вывод простой: fetch failed — это не отказ сервера, это отсутствие ответа. Разница принципиальная. Отказ надо чинить, отсутствие ответа — повторять.
Что стоит сделать в скрипте, если вы наступите на то же:
Печатать
e.cause, а не толькоe.message. Одна строка, а диагностика перестаёт быть гаданием.Повторить запрос. У меня на третьем запуске шлюз ответил
200.Если и повтор молчит — отправить тем же curl, перечислив только изменившиеся страницы. Список из одного адреса протокол принимает так же охотно, как список из тридцати.
Дублировать отправку на оба шлюза, как в скрипте выше, стоит именно поэтому. Общий шлюз раздаёт уведомление всем участникам, но если он не отвечает, прямая точка Яндекса вытягивает главное. Дубль протоколом не штрафуется.
Грабля из соседней области: cleanUrls на Vercel ломает подтверждение прав
Раз уж речь о подтверждении прав файлом в корне — рядом лежит грабля, на которую я потратил больше времени, чем на всё остальное.
В Яндекс Вебмастере и Google Search Console права на сайт подтверждаются одним из трёх способов: файл в корне, метатег в <head>, запись в DNS. Способ с файлом выглядит самым простым — и на статике, разложенной на Vercel с включённым cleanUrls, он не работает в принципе.
cleanUrls заставляет хостинг отдавать 308 Permanent Redirect с любого адреса вида /что-нибудь.html на /что-нибудь. Робот, который пришёл за файлом подтверждения, редиректа не ждёт: он ждёт 200 и содержимое. Получает 308 — и говорит «файл не найден».
Лечится метатегом в самом начале <head>. И его тоже нельзя потом удалить — права слетят молча, а узнаете вы об этом через месяц.
Второе наблюдение из той же истории: проверка прав в Яндекс Вебмастере иногда показывает результат предыдущей попытки и утверждает, что метатег не найден, когда он на месте. Прежде чем чинить несуществующую проблему, откройте Инструменты → Проверка ответа сервера: там видно исходный код страницы глазами робота Яндекса. У меня тег стоял всё это время, а проверка прошла с третьего раза.
Замечу, что файл ключа IndexNow при этом отдаётся нормально: у него расширение .txt, а cleanUrls трогает только .html.
Чего IndexNow не делает
Это самая полезная часть, и в документации её нет.
IndexNow ускоряет обход, а не включение в индекс. Робот приходит быстрее — это правда и это измеримо. Но решение «показывать ли страницу в поиске» принимается отдельно и позже, и на него IndexNow не влияет никак.
Честные цифры с моего домена, которому на тот момент был месяц: 33 адреса отправлены, оба шлюза приняли, обход прошёл, ошибок и исключённых страниц у Вебмастера ноль. В поиске при этом три страницы из тридцати трёх. Не потому что что-то сломано — потому что у молодого домена очередь на включение измеряется неделями, и никакой протокол её не сокращает.
Так что если вы пришли за инструментом, который вытащит новый сайт в выдачу за выходные, — это не он. IndexNow снимает ровно одну задачу: «робот не знает, что у меня что-то поменялось». Задачу «робот знает, но не считает нужным показывать» он не решает.
Зато он бесплатный, ставится за полчаса, не требует ни одного аккаунта и после первой настройки работает одной командой в конце деплоя. За такую цену — берите.