**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 продукту                  |
| Змінено slug продукту                | Сповіщайте про ОБИДВА старий і новий 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 знову працює).

## Обробка змін slug

Звичайний тонкий випадок: slug продукту змінюється з `/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 Webmaster Tools має спеціальну панель IndexNow:

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

Yandex Webmaster має еквівалентну звітність у розділі **Індексація**.

## Як 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 для аудиту.