React

revalidateTag: invalidare i dati giusti

Un tag collega una fetch alla sua invalidazione: revalidateTag cancella le voci con quel tag, non una pagina. Dove chiamarlo e cosa rompe.

In sintesi

  • Un tag è un'etichetta sulla fetch: revalidateTag colpisce tutte le voci con quell'etichetta.
  • Chiamalo da una Server Action o da un Route Handler, non durante il render.
  • Da Next.js 15 la fetch non è in cache per default: senza cache il tag non fa nulla.
  • Su più istanze la cache è per processo, quindi serve un handler condiviso.

Un tag è un'etichetta che colleghi a una fetch, e revalidateTag invalida tutte le voci che portano quell'etichetta. Non è un refresh della pagina: è una cancellazione mirata che vale per l'intera applicazione.

Il giro completo

// lib/products.ts
export async function getProducts() {
  const res = await fetch("https://api.example.com/products", {
    next: { tags: ["products"], revalidate: 3600 },
  });
  return res.json();
}

L'etichetta vive nell'opzione next della fetch, non nella risposta. Ogni pagina che chiama getProducts condivide la stessa voce di cache e lo stesso tag: una sola invalidazione le copre tutte.

Dove chiamarlo

// app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";
import { NextResponse } from "next/server";

const TAGS = new Set(["products", "orders"]);

export async function POST(request: Request) {
  const secret = request.headers.get("x-revalidate-secret");
  if (secret !== process.env.REVALIDATE_SECRET) {
    return NextResponse.json({ ok: false }, { status: 401 });
  }
  const tag = new URL(request.url).searchParams.get("tag") ?? "";
  if (!TAGS.has(tag)) {
    return NextResponse.json({ ok: false, reason: "unknown tag" }, { status: 400 });
  }
  revalidateTag(tag);
  return NextResponse.json({ ok: true, tag });
}

Il segreto e la allowlist non sono cerimonia: chi chiama quell'endpoint può svuotare la cache. Con pochi tag, la lista chiusa costa due righe e toglie la superficie di attacco.

La chiamata

curl -X POST "http://localhost:3000/api/revalidate?tag=products" \
  -H "x-revalidate-secret: $REVALIDATE_SECRET"

Tag, path o tempo

StrumentoCosa invalidaQuando usarlo
revalidateTagTutte le fetch con quel tagIl dato cambia e più pagine lo leggono
revalidatePathLa cache di un percorsoUna sola pagina è cambiata
revalidate: nLa singola fetch dopo n secondiIl dato invecchia bene da solo
router.refresh()Il client, non la cacheVuoi rileggere dal server dopo una mutazione

Gli errori tipici

  • Chiamare revalidateTag durante il render di un server component: il runtime lo rifiuta.
  • Mettere il tag sulla risposta invece che sulla fetch: l'etichetta va nell'opzione next.
  • Assumere che il tag conosca la lingua: products vale per tutti i locale. Se il prezzo cambia solo in Italia, usa products-it.
  • Dimenticare che Next.js 15 non mette in cache la fetch per default: senza next.revalidate o cache: "force-cache" il tag non ha effetto.

I casi in cui un dato vecchio si nota subito sono tabelle dati, statistiche e un blocco tabella.

I livelli di cache sono in i quattro livelli di cache; il comportamento HTTP in stale-while-revalidate e header di cache. Confronto: Next.js vs Vite.

Limite noto: il Data Cache vive nel processo che ha eseguito la fetch. Con due istanze, l'invalidazione su una non tocca l'altra: serve un cache handler condiviso o un TTL breve.

Domande frequenti

revalidateTag e revalidatePath sono equivalenti?

No. Il tag invalida tutte le fetch che lo portano, il path invalida la cache di una rotta. Usa il tag quando lo stesso dato alimenta più pagine.

Posso chiamarlo da un client component?

No. La funzione gira sul server: chiamala da una Server Action o da un Route Handler, poi aggiorna il client con router.refresh.

Perché il tag sembra non fare nulla?

Perché la fetch non è in cache. Da Next.js 15 la cache è opt-in: aggiungi next.revalidate o cache force-cache alla fetch.

Quanti tag servono?

Uno per entità che cambia insieme. Se due dati si aggiornano sempre nello stesso momento, un tag solo evita invalidazioni doppie.

Il tag è per lingua?

Non lo è per default. Se il contenuto cambia per locale, includi il locale nel nome, per esempio products-it.