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
| Strumento | Cosa invalida | Quando usarlo |
|---|---|---|
revalidateTag | Tutte le fetch con quel tag | Il dato cambia e più pagine lo leggono |
revalidatePath | La cache di un percorso | Una sola pagina è cambiata |
revalidate: n | La singola fetch dopo n secondi | Il dato invecchia bene da solo |
router.refresh() | Il client, non la cache | Vuoi rileggere dal server dopo una mutazione |
Gli errori tipici
- Chiamare
revalidateTagdurante 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:
productsvale per tutti i locale. Se il prezzo cambia solo in Italia, usaproducts-it. - Dimenticare che Next.js 15 non mette in cache la fetch per default: senza
next.revalidateocache: "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.