**TL;DR.** IndexNow è un'API standardizzata e gratuita che ti consente di notificare istantaneamente Bing, Yandex, Seznam e Naver delle modifiche agli URL. Per un sito di e-commerce, ciò significa che un nuovo prodotto appare in Bing in pochi minuti anziché in giorni. Implementalo come una coda svuotata da un lavoro in background, non come chiamate sincrone.

## Cos'è IndexNow

IndexNow è un protocollo aperto annunciato da Microsoft e Yandex nel 2021. I motori di ricerca che partecipano accettano un semplice HTTP POST che elenca gli URL che sono cambiati; eseguono la scansione di quegli URL con priorità.

Motori partecipanti:

- Bing
- Yandex
- Seznam (Ceco)
- Naver (Coreano)

Assente notoriamente: Google. Google non partecipa a IndexNow; per Google ti affidi alla scansione regolare o all'API di indicizzazione (che è limitata agli annunci di lavoro, ai livestream e ad alcuni altri tipi di contenuto).

## Come funziona

1. Generi una chiave API (una stringa casuale di almeno 32 caratteri).
2. Ospiti la chiave su `https://yourdomain.com/{key}.txt` — il corpo contiene solo la chiave. Questo verifica che controlli il dominio.
3. Effettui un POST su `https://api.indexnow.org/indexnow` (o su qualsiasi endpoint IndexNow di un motore partecipante) con:

```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. Il motore mette in coda gli URL per la scansione.

Ecco fatto. Nessuna autenticazione oltre al controllo della proprietà del file chiave, nessun sistema di token di limitazione della frequenza.

## Quando inviare un ping

Invia un ping a IndexNow ogni volta che un URL indicizzabile pubblicamente cambia in modo significativo:

| Evento                                | Ping                                         |
| ------------------------------------- | -------------------------------------------- |
| Prodotto creato                       | Invia il ping del nuovo URL del prodotto     |
| Prodotto aggiornato (titolo, descrizione) | Invia il ping dell'URL del prodotto          |
| Slug del prodotto cambiato            | Invia il ping di ENTRAMBI gli URL vecchio e nuovo |
| Prodotto non pubblicato o eliminato   | Invia il ping dell'URL (il motore vedrà 404/410 e lo eliminerà) |
| Categoria creata                      | Invia il ping dell'URL della categoria      |
| Categoria aggiornata                  | Invia il ping dell'URL della categoria      |
| Marca o pagina creata/aggiornata     | Invia il ping dell'URL                       |
| Sitemap rigenerato                    | Facoltativamente invia il ping dell'URL della sitemap |

Non inviare ping per:

- Aggiornamenti di inventario che non cambiano il contenuto.
- Modifiche solo interne (registro di audit dei prezzi, azioni di amministrazione).
- Importazioni in blocco — raggruppa il ping post-importazione.

## Coda, non chiamare sincronicamente

Un'implementazione ingenua:

```ts
// CATTIVO: chiamata sincrona a IndexNow all'interno del salvataggio del prodotto
async function saveProduct(product: Product) {
  await db.update(...);
  await fetch('https://api.indexnow.org/indexnow', { method: 'POST', body: ... });
  // ↑ aggiunge 100–500ms a ogni salvataggio
}
```

Meglio:

```ts
// BUONO: accoda, svuota in modo asincrono
async function saveProduct(product: Product) {
  await db.update(...);
  await enqueueIndexNow(product.url);
  // restituisce immediatamente
}

// Compito in background, eseguito ogni 1–5 minuti
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);
  },
});
```

Vantaggi della coda:

- Le mutazioni rimangono veloci.
- I fallimenti non interrompono i flussi visibili agli utenti.
- Facile da raggruppare.
- Facile da registrare per ogni tentativo.
- Sopravvive ai tempi di inattività della piattaforma (la coda persiste, si svuota quando l'API è di nuovo attiva).

## Gestione delle modifiche allo slug

Un caso comune e sottile: lo slug di un prodotto cambia da `/products/old` a `/products/new`. Accoda ENTRAMBI gli URL:

- Il nuovo URL ha bisogno di essere scansionato affinché il motore lo aggiunga all'indice.
- Il vecchio URL ha bisogno di essere scansionato affinché il motore lo recuperi nuovamente, veda il 301 (o 410) e lo elimini.

```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}`]);
}
```

Per le eliminazioni, scrivi un reindirizzamento 410 (tabella dei percorsi eliminati di Ordiko) e invia il ping dell'URL. Il motore vede 410 e elimina l'URL dal suo indice permanentemente.

## Raggruppamento

L'API accetta fino a 10.000 URL per POST. Modello comune di e-commerce: svuotare 100–500 URL al minuto, a seconda del volume di mutazioni.

```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'); // riaccoda per il prossimo ciclo di svuotamento
      } else {
        await markAsDrained(urls, 'failed', `${res.status} ${await res.text()}`);
      }
    } catch (err) {
      await markAsDrained(urls, 'failed', String(err));
    }
  },
});
```

## Registrazione

Ogni ping (successo o fallimento) dovrebbe essere registrato. Schema utile per la tabella di audit:

```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()
);
```

Questo ti consente di rispondere a "perché questo prodotto non appare in Bing?" interrogando la tabella per l'URL del prodotto.

## Verifica

Bing Webmaster Tools ha un dashboard dedicato a IndexNow:

1. Accedi su [bing.com/webmasters](https://www.bing.com/webmasters).
2. Aggiungi il tuo sito.
3. Naviga su **IndexNow** nella barra laterale.
4. Visualizza le sottomissioni nel tempo, conteggi di successi/fallimenti.

Yandex Webmaster ha report equivalenti sotto **Indicizzazione**.

## Come Ordiko implementa IndexNow

- La colonna `stores.indexNowApiKey` memorizza la chiave per ogni negozio.
- Il file chiave è servito automaticamente su `/{key}.txt`.
- Ogni servizio entità (`product.service.ts`, `category.service.ts`, ecc.) chiama `notifyIndexNowOnChange(url)` in caso di mutazione.
- Coda: tabella `pending_indexnow` con `(storeId, url, enqueuedAt)`.
- Il compito cron di Trigger.dev `indexnow-drain.task.ts` viene eseguito ogni minuto.
- Ogni svuotamento viene registrato in `seo_revalidation_events` con `step: "indexnow", status: ok|failed|retry`.

## FAQ

**IndexNow funziona per Google?**
No. Google non partecipa a IndexNow. Per Google, invia la tua sitemap XML e fai affidamento sui cicli di scansione regolari, oppure utilizza l'API di indicizzazione di Google per tipi di contenuto limitati (annunci di lavoro, eventi in diretta). Per Bing, Yandex, Seznam e Naver, IndexNow è il modo più veloce per segnalare aggiornamenti.

**Quanti URL posso inviare per chiamata?**
Fino a 10.000 URL per POST. L'API restituisce 200-OK per le sottomissioni accettate. Per volumi maggiori, raggruppa e limita la frequenza — il tasso sicuro tipico è di 1–10 batch al minuto. Il lavoro di svuotamento in Ordiko invia un batch al minuto per impostazione predefinita.

**Cosa succede se invio in modo troppo aggressivo?**
Ricevi 429 Too Many Requests. L'API non ti banna — rallenta, riprova con un backoff esponenziale. L'invio sostenuto di URL invariati ad alto volume può portare a una de-prioritizzazione ma non a un blocco.

**Come implementa Ordiko IndexNow?**
Ogni servizio entità (prodotto, categoria, marca, pagina) chiama notifyIndexNowOnChange in caso di creazione/aggiornamento/eliminazione/non pubblicazione. La tabella di coda pending_indexnow contiene le voci; un compito cron di Trigger.dev indexnow-drain.task.ts la svuota secondo un programma. Ogni ping è registrato in seo_revalidation_events per audit.