React
I quattro livelli di cache di Next.js
Memoizzazione, Data Cache, Full Route Cache e Router Cache: cosa conserva ogni livello, per quanto tempo e come si svuota davvero.
In sintesi
- Le quattro cache hanno chiavi diverse: richiesta, URL, route e client.
- Il build dice quali route sono statiche: è la prima prova da leggere.
- `revalidatePath` invalida il server, `router.refresh()` il browser: servono entrambi dopo una scrittura.
Next.js non ha una cache: ne ha quattro, con chiavi e durate diverse. Sono la memoizzazione della richiesta, la Data Cache, la Full Route Cache e la Router Cache. Confonderle è la causa più frequente di dati vecchi in pagina.
Le quattro cache, in ordine
| Livello | Cosa conserva | Vita | Come si svuota |
|---|---|---|---|
| Memoizzazione della richiesta | Risultato di una fetch ripetuta nello stesso render | Un solo render | Si azzera da sola |
| Data Cache | Risposta di una fetch, con chiave URL e opzioni | Finché revalidate non scade | revalidateTag, revalidatePath |
| Full Route Cache | HTML e payload RSC di una route statica | Fino al prossimo build o alla rivalidazione | revalidatePath, rebuild |
| Router Cache | Payload RSC già visitati, lato browser | Fino a 5 minuti sulle route statiche | router.refresh() |
Il build dichiara quale route è statica e quale è dinamica. Quella riga è la prima cosa da leggere quando un dato non si aggiorna: se la route è segnata come statica, il problema non è il browser.
npm run build
La memoizzazione non è una cache
Due chiamate con gli stessi argomenti nello stesso render producono una richiesta sola. Non sopravvive alla risposta: non c’è niente da invalidare e niente da configurare. Per una query al database si ottiene lo stesso effetto con cache() di React, che deduplica la funzione per il singolo render.
import { cache } from "react";
import { db } from "@/lib/db";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});
Data Cache: la chiave è la richiesta
La Data Cache conserva la risposta di una fetch sul server. La chiave è l’URL più le opzioni, quindi la stessa rotta chiamata con un header diverso non condivide la voce. Con revalidate: 60 la risposta resta valida un minuto; con cache: "no-store" non entra affatto.
const res = await fetch(`https://api.example.com/products/${id}`, {
next: { revalidate: 60, tags: [`product-${id}`] },
});
Un tag è la parte che rende la cache manutenibile: invalidi per nome del dato, non per elenco di URL. La chiamata seguente rigenera solo le voci con quel tag.
"use server";
import { revalidateTag } from "next/cache";
export async function updateProduct(id: string, data: ProductInput) {
await db.product.update({ where: { id }, data });
revalidateTag(`product-${id}`);
}
Full Route Cache: la pagina intera
Una route senza API dinamiche viene renderizzata al build e servita come HTML più payload RSC. Basta una cookies(), una headers() o una searchParams letta sul server per farla diventare dinamica. Non è un interruttore da mettere a caso: è la conseguenza di quali dati la pagina usa.
Router Cache: quello che resta nel browser
Il client tiene in memoria i payload delle route già visitate, così il tasto indietro non richiede il server. Dopo una scrittura servono due gesti distinti: revalidatePath invalida la cache del server, router.refresh() quella del browser. Il secondo non aggiorna il database e il primo non tocca la scheda aperta.
Il sito come caso misurato
Questo blog è il banco di prova: 299 file JSON, letti a ogni richiesta e serviti da una finestra di cache di 5 secondi nel server Express. La finestra corta è una scelta, non un default: con 299 articoli il file system risponde in pochi millisecondi e la cache lunga nasconderebbe una correzione appena pubblicata.
Dove entrano i componenti del catalogo
Una UI che mostra dati cacheati ha bisogno di stati vuoti, skeleton e filtri che vivono nell’URL. I blocchi dashboard coprono il primo caso, la libreria nuqs il terzo, e le primitive di shadcn il resto. Sono pezzi di interfaccia: la politica di cache resta nella route.
Dove si sbaglia
- Cacheare una fetch autenticata senza mettere l’utente nella chiave: il secondo utente riceve i dati del primo.
- Usare
no-storeovunque per sicurezza: si perde la cache e non si guadagna nessuna protezione. - Chiamare
revalidatePath("/")per un solo record: si rigenera tutto il sito per un dato. - Fidarsi del Router Cache dopo un logout: la pagina in memoria resta finché non la invalidi.
Un limite da dichiarare
Queste cache vivono nel processo Node. Con due istanze, revalidateTag svuota la cache dell’istanza che riceve la chiamata e lascia le altre con la voce vecchia: serve un cache handler condiviso, oppure una scadenza breve che renda l’errore innocuo. Il limite è dell’infrastruttura, non del framework.
Per sapere quale route è statica e quale no, la struttura di un progetto App Router è il punto di partenza; gli errori di cache dopo il deploy sono raccolti in 404 dopo il deploy. Se stai valutando un altro framework, il confronto Remix vs Next.js parte dai modelli di cache.
Domande frequenti
Quante cache ha Next.js?
Quattro: memoizzazione della richiesta, Data Cache, Full Route Cache e Router Cache. Ognuna ha una chiave e una durata diverse.
Come svuoto la cache dopo una modifica?
Con revalidateTag se il dato ha un tag, revalidatePath se serve tutta la route. Per il browser aggiungi router.refresh() dopo la mutazione.
Perché la mia pagina è sempre dinamica?
Perché legge cookies, headers o searchParams sul server, oppure usa una fetch senza cache. Una sola di queste chiamate toglie la Full Route Cache.
La Data Cache funziona con un database?
Non direttamente: cachea le fetch. Per le query usa cache() di React nello stesso render, oppure una funzione con la tua cache e i tuoi tag.
Serve un Redis per la cache?
No con una sola istanza: la cache in memoria del processo basta. Con più istanze serve un handler condiviso, altrimenti l’invalidazione resta locale.