**TL;DR.** IndexNow — это бесплатный стандартизированный API, который позволяет мгновенно уведомлять Bing, Yandex, Seznam и Naver об изменениях URL. Для интернет-магазина это означает, что новый продукт появляется в Bing за считанные минуты, а не дни. Реализуйте это как очередь, которую обрабатывает фоновая задача, а не синхронные вызовы.

## Что такое IndexNow

IndexNow — это открытый протокол, объявленный Microsoft и Yandex в 2021 году. Поисковые системы, которые участвуют, принимают простой HTTP POST с перечислением изменившихся URL; они придают этим URL приоритет при обходе.

Участвующие поисковые системы:

- Bing
- Yandex
- Seznam (Чехия)
- Naver (Корея)

Отсутствует: Google. Google не участвует в IndexNow; для Google вы полагаетесь на регулярный обход или API индексации (который ограничен вакансиями, прямыми трансляциями и несколькими другими типами контента).

## Как это работает

1. Вы генерируете API-ключ (любая случайная строка длиной 32 символа и более).
2. Вы размещаете ключ по адресу `https://yourdomain.com/{key}.txt` — тело содержит только ключ. Это подтверждает, что вы контролируете домен.
3. Вы отправляете POST-запрос на `https://api.indexnow.org/indexnow` (или на любой конечный пункт IndexNow участвующей поисковой системы) с:

```json
{
  "host": "yourdomain.com",
  "key": "your-api-key",
  "keyLocation": "https://yourdomain.com/your-api-key.txt",
  "urlList": [
    "https://yourdomain.com/products/leather-bag",
    "https://yourdomain.com/products/wool-coat"
  ]
}
```

4. Поисковая система ставит URL в очередь для обхода.

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

## Когда отправлять уведомление

Отправляйте уведомление в IndexNow всякий раз, когда публично индексируемый URL изменяется значимо:

| Событие                              | Уведомление                                   |
| ------------------------------------- | --------------------------------------------- |
| Продукт создан                        | Уведомить о новом URL продукта                |
| Продукт обновлен (название, описание) | Уведомить о URL продукта                      |
| Изменен слаг продукта                | Уведомить О BOTH старом и новом URL          |
| Продукт снят с публикации или удален | Уведомить о URL (поисковая система увидит 404/410 и удалит) |
| Категория создана                     | Уведомить о URL категории                     |
| Категория обновлена                   | Уведомить о URL категории                     |
| Бренд или страница созданы/обновлены  | Уведомить о URL                              |
| Карта сайта регенерирована           | По желанию уведомить о URL карты сайта       |

Не отправляйте уведомления для:

- Обновлений инвентаря, которые не изменяют контент.
- Изменений только для внутреннего использования (журнал аудита цен, действия администраторов).
- Массового импорта — группируйте уведомления после импорта.

## Используйте очередь, не вызывайте синхронно

Наивная реализация:

```ts
// ПЛОХО: синхронный вызов IndexNow внутри сохранения продукта
async function saveProduct(product: Product) {
  await db.update(...);
  await fetch('https://api.indexnow.org/indexnow', { method: 'POST', body: ... });
  // ↑ добавляет 100–500 мс к каждому сохранению
}
```

Лучше:

```ts
// ХОРОШО: добавляем в очередь, обрабатываем асинхронно
async function saveProduct(product: Product) {
  await db.update(...);
  await enqueueIndexNow(product.url);
  // возвращает немедленно
}

// Фоновая задача, выполняется каждые 1–5 минут
export const indexNowDrainTask = task({
  id: 'indexnow-drain',
  cron: '*/1 * * * *',
  run: async () => {
    const urls = await fetchPendingUrls(MAX_BATCH);
    if (urls.length === 0) return;
    await postToIndexNow(urls);
    await markAsDrained(urls);
  },
});
```

Преимущества использования очереди:

- Мутации остаются быстрыми.
- Ошибки не нарушают пользовательские потоки.
- Легко группировать.
- Легко вести учет по каждой попытке.
- Переживает время простоя платформы (очередь сохраняется, обрабатывается, когда API снова доступен).

## Обработка изменений слага

Распространенный тонкий случай: слаг продукта изменяется с `/products/old` на `/products/new`. Добавьте в очередь ОБА URL:

- Новый URL нуждается в обходе, чтобы поисковая система добавила его в индекс.
- Старый URL нуждается в обходе, чтобы поисковая система повторно извлекла его, увидела 301 (или 410) и удалила его.

```ts
async function updateProductSlug(productId: string, oldSlug: string, newSlug: string) {
  await db.products.update({ id: productId, slug: newSlug });
  await writeRedirect({ from: `/products/${oldSlug}`, to: `/products/${newSlug}`, status: 301 });
  await enqueueIndexNow([`/products/${oldSlug}`, `/products/${newSlug}`]);
}
```

Для удалений напишите редирект 410 (таблица ушедших путей Ordiko) и уведомите о URL. Поисковая система увидит 410 и навсегда удалит URL из своего индекса.

## Пакетирование

API принимает до 10,000 URL за один POST. Распространенный шаблон для интернет-магазинов: обрабатывайте 100–500 URL в минуту, в зависимости от объема мутаций.

```ts
const MAX_BATCH = 500;
const BATCH_INTERVAL_MS = 60_000;

export const indexNowDrainTask = task({
  id: 'indexnow-drain',
  cron: '*/1 * * * *',
  run: async () => {
    const urls = await fetchPendingUrls(MAX_BATCH);
    if (urls.length === 0) return;

    try {
      const res = await fetch('https://api.indexnow.org/indexnow', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          host: 'yourdomain.com',
          key: process.env.INDEXNOW_KEY,
          keyLocation: `https://yourdomain.com/${process.env.INDEXNOW_KEY}.txt`,
          urlList: urls,
        }),
      });

      if (res.status === 200 || res.status === 202) {
        await markAsDrained(urls, 'ok');
      } else if (res.status === 429) {
        await markAsDrained(urls, 'retry'); // повторная очередь для следующего цикла обработки
      } else {
        await markAsDrained(urls, 'failed', `${res.status} ${await res.text()}`);
      }
    } catch (err) {
      await markAsDrained(urls, 'failed', String(err));
    }
  },
});
```

## Логирование

Каждое уведомление (успех или ошибка) должно быть зафиксировано. Полезная схема таблицы аудита:

```sql
CREATE TABLE seo_revalidation_events (
  id SERIAL PRIMARY KEY,
  store_id UUID NOT NULL,
  url TEXT NOT NULL,
  step TEXT NOT NULL, -- 'indexnow' | 'revalidate_tag' | 'sitemap'
  status TEXT NOT NULL, -- 'ok' | 'failed' | 'retry'
  error TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);
```

Это позволяет вам ответить на вопрос "почему этот продукт не отображается в Bing?", запрашивая таблицу по URL продукта.

## Проверка

Инструменты вебмастера Bing имеют специальную панель управления IndexNow:

1. Войдите на [bing.com/webmasters](https://www.bing.com/webmasters).
2. Добавьте свой сайт.
3. Перейдите в **IndexNow** в боковом меню.
4. Просмотрите отправки с течением времени, количество успешных/неуспешных.

Инструменты вебмастера Yandex имеют эквивалентную отчетность в разделе **Индексация**.

## Как Ordiko реализует IndexNow

- Столбец `stores.indexNowApiKey` хранит ключ для каждого магазина.
- Файл ключа автоматически предоставляется по адресу `/{key}.txt`.
- Каждый сервис сущности (`product.service.ts`, `category.service.ts` и т.д.) вызывает `notifyIndexNowOnChange(url)` при мутации.
- Очередь: таблица `pending_indexnow` с `(storeId, url, enqueuedAt)`.
- Задача cron Trigger.dev `indexnow-drain.task.ts` выполняется каждую минуту.
- Каждая обработка записывается в `seo_revalidation_events` с `step: "indexnow", status: ok|failed|retry`.

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

**Работает ли IndexNow для Google?**
Нет. Google не участвует в IndexNow. Для Google отправьте свою XML-карту сайта и полагайтесь на регулярные циклы обхода или используйте собственный API индексации Google для ограниченных типов контента (вакансии, события прямых трансляций). Для Bing, Yandex, Seznam и Naver IndexNow — это самый быстрый способ сигнализировать об обновлениях.

**Сколько URL я могу отправить за один вызов?**
До 10,000 URL за один POST. API возвращает 200-OK для принятых отправок. Для больших объемов группируйте и ограничивайте частоту — типичная безопасная частота составляет 1–10 партий в минуту. Задача обработки в Ordiko отправляет одну партию в минуту по умолчанию.

**Что произойдет, если я отправлю слишком агрессивно?**
Вы получите 429 Слишком много запросов. API не блокирует вас — отступите, повторите с экспоненциальным увеличением времени ожидания. Постоянная отправка большого объема неизмененных URL может привести к понижению приоритета, но не к блокировке.

**Как Ordiko реализует IndexNow?**
Каждый сервис сущности (продукт, категория, бренд, страница) вызывает notifyIndexNowOnChange при создании/обновлении/удалении/снятии с публикации. Таблица очереди pending_indexnow содержит записи; задача cron Trigger.dev indexnow-drain.task.ts обрабатывает ее по расписанию. Каждое уведомление записывается в seo_revalidation_events для аудита.