WebLa cache di Next.js 16: use cache, cacheLife e updateTag
In Next.js 16 niente va in cache finché non scrivi 'use cache'. Come lavorano i sette profili di cacheLife, cacheTag e updateTag, e cosa cambia per fetch.

Il modello di cache di Next.js 16 è un sistema opt-in: niente finisce in cache finché non marchi una funzione, un componente o un file con la direttiva 'use cache', e ogni voce in cache ha una durata fissata da cacheLife e, se serve, dei tag da invalidare dopo una scrittura. Al posto delle cache implicite delle prime versioni dell'App Router ci sono una direttiva e poche funzioni. Si attiva con un solo flag, cacheComponents: true, e lo stesso flag rende il Partial Prerendering il modo predefinito di renderizzare una route.
Riguarda ogni team che lavora con l'App Router e si è chiesto perché una pagina mostrava dati vecchi, o perché una pagina pensata come statica veniva renderizzata a ogni richiesta. Le due domande avevano la stessa causa: decideva il framework cosa mettere in cache, e la decisione era difficile da vedere. In Next.js 16 la decisione sta nel codice, accanto ai dati a cui si applica. Qui descriviamo il modello come lo documenta Next.js 16.4, uscito il 6 ottobre 2026.
La versione in trenta secondi
- Dinamico per default. Con Cache Components attivo, ogni fetch e ogni query al database gira al momento della richiesta, a meno che non stia dentro uno scope
'use cache'. - Una durata per ogni voce.
cacheLife('hours'), o un profilo tuo, imposta tre tempi: quanto a lungo il browser riusa la voce, quando il server la rigenera in background e quando scade. - Invalidazione per tag.
cacheTag('posts')etichetta una voce. Dopo una scrittura, conupdateTag('posts')la lettura successiva aspetta i dati freschi; conrevalidateTag('posts', 'max')si serve la copia vecchia mentre se ne prepara una nuova. - La shell statica nasce dalla cache. L'output in cache con una durata abbastanza lunga entra nel prerender della route. Le voci di breve durata diventano buchi che si riempiono al momento della richiesta.
Perché Next.js ha cambiato di nuovo il modello di cache?
Perché i default sorprendevano. In Next.js 14 fetch usava force-cache salvo indicazione contraria, le route handler GET finivano in cache e il router lato client teneva in memoria i segmenti di pagina tra una navigazione e l'altra. Una pagina poteva servire dati vecchi senza che una sola riga di codice lo dicesse. Next.js 15 ha ribaltato quei default: fetch, handler GET e navigazioni lato client non vanno più in cache se non lo chiedi tu.
La sorpresa è sparita, ma gli strumenti sono rimasti sparsi: l'opzione cache di fetch, next.revalidate, le configurazioni di segmento dynamic, revalidate e fetchCache, e unstable_cache per tutto ciò che non era una fetch. Next.js 16, uscito a ottobre 2025, li ha riuniti in una sola direttiva. Con Cache Components attivo, la guida ufficiale alla migrazione chiede di togliere dynamic, revalidate e fetchCache dalle route e di spostare la scelta sulla cache direttamente sui dati.
Come funziona 'use cache'?
'use cache' marca come memorizzabile una funzione async, un componente async o un intero file. La prima chiamata con certi input esegue il corpo e salva il risultato. Le chiamate successive con gli stessi input lo riusano, dentro lo stesso render e tra richieste diverse, finché la voce non scade.
import { cacheLife, cacheTag } from 'next/cache'
export async function getProject(id: string) {
'use cache'
cacheLife('hours')
cacheTag('projects', `project-${id}`)
return db.project.findUnique({ where: { id } })
}La chiave di cache si compone di quattro elementi: l'ID della build, un hash della posizione e della firma della funzione, gli argomenti serializzati e le variabili che la funzione cattura dallo scope esterno. Ne seguono due conseguenze. Argomenti e valori di ritorno devono essere serializzabili, quindi niente istanze di classe e niente connessioni aperte. E ogni nuovo deploy parte con la cache vuota, perché l'ID della build entra in ogni chiave.
Uno scope in cache non può leggere cookies(), headers() o searchParams, e il divieto segue lo stack delle chiamate: anche un helper che legge un cookie fallisce se lo chiami da una funzione in cache. La documentazione avverte che su una route dinamica l'errore può passare indenne next build e comparire solo con next start. La soluzione è leggere i dati della richiesta fuori e passarli come argomento; così entrano anche nella chiave.
import { cookies } from 'next/headers'
export default async function Page() {
const teamId = (await cookies()).get('team')?.value ?? ''
const projects = await getProjects(teamId) // cached per teamId
return <ProjectList projects={projects} />
}Quanto dura una voce in cache?
cacheLife imposta tre tempi. stale è quanto a lungo il browser riusa la voce senza interrogare il server. revalidate è il momento in cui la richiesta successiva avvia una rigenerazione in background, ricevendo comunque la copia in cache. expire è il momento in cui una voce che nessuno ha richiesto viene scartata, e la richiesta successiva aspetta l'output fresco. Next.js offre sette profili predefiniti:
default: stale 5 minuti, revalidate 15 minuti, nessuna scadenza. Vale quando uno scope non chiamacacheLife.seconds: stale 30 secondi, revalidate 1 secondo, expire 1 minuto.minutes: stale 5 minuti, revalidate 1 minuto, expire 1 ora.hours: stale 5 minuti, revalidate 1 ora, expire 1 giorno.days: stale 5 minuti, revalidate 1 giorno, expire 1 settimana.weeks: stale 5 minuti, revalidate 1 settimana, expire 30 giorni.max: stale 5 minuti, revalidate 30 giorni, expire 1 anno.
Ogni profilo si può ridefinire, e se ne possono aggiungere di nuovi con un nome in next.config.ts. La documentazione consiglia di chiamare cacheLife in ogni scope in cache, e siamo d'accordo: un default implicito è la prima cosa che nessuno ricorda sei mesi dopo.
La durata decide anche dove può finire l'output. Una voce con revalidate a 0 o expire sotto i 5 minuti resta fuori dal prerender e diventa un buco dinamico. Resta fuori anche una voce con stale sotto i 30 secondi, perché il prefetch scadrebbe prima del clic. Tra i profili predefiniti, solo seconds supera queste soglie. Lato client, il router tiene ogni voce per almeno 30 secondi, qualunque sia la configurazione.
Come si invalida la cache dopo una scrittura?
La scadenza a tempo va bene per i contenuti che cambiano poco a poco. Per i dati che un utente ha appena modificato serve un'invalidazione su richiesta, e Next.js 16 ne offre tre, ognuna con una garanzia diversa:
updateTag(tag)fa scadere subito tutte le voci con quel tag, e la lettura successiva aspetta i dati freschi. Funziona solo dentro le Server Action. È lo strumento per i form: l'utente rinomina un progetto e vede il nome nuovo, non il vecchio.revalidateTag(tag, 'max')segna le voci come scadute. La richiesta successiva riceve ancora la copia vecchia mentre in background se ne prepara una fresca. Funziona nelle Server Action e nelle Route Handler. È lo strumento per webhook e pubblicazioni dal CMS, dove qualche secondo di contenuto vecchio non costa niente. Il secondo argomento ora è obbligatorio; la forma con un solo argomento è deprecata.refresh()non tocca nessuna voce in cache. Da una Server Action chiede al router lato client di ricaricare la pagina corrente, per i dati che non sono mai stati in cache.
'use server'
import { updateTag } from 'next/cache'
export async function renameProject(id: string, name: string) {
await db.project.update({ where: { id }, data: { name } })
updateTag(`project-${id}`)
}import { revalidateTag } from 'next/cache'
export async function POST(request: Request) {
const { slug } = await request.json()
revalidateTag(`post-${slug}`, 'max')
return Response.json({ ok: true })
}Il lavoro di progettazione vero sta nei tag. cacheTag accetta più valori: conviene etichettare ogni voce con la sua collezione (projects) e con il suo record (project-42). Una lista si invalida con il primo, una pagina di dettaglio con il secondo. I tag distinguono maiuscole e minuscole e hanno un limite di 256 caratteri; un tag più lungo non viene mai assegnato, quindi invalidarlo non fa niente e non segnala errori.
E fetch, adesso?
Con Cache Components attivo, fetch non va in cache a meno che non giri dentro uno scope 'use cache'. La guida alla migrazione sposta cache: 'force-cache' in una funzione in cache, next.revalidate in cacheLife e next.tags in cacheTag. unstable_cache continua a funzionare, quindi una codebase grande può migrare una funzione di dati alla volta.
C'è una differenza che sfugge facilmente. La cache di fetch e unstable_cache possono sopravvivere a un deploy; le voci di 'use cache' no, perché l'ID della build sta nella chiave. Se fai dieci deploy al giorno contro un'API esterna lenta, ogni deploy riparte a freddo. Per i dati che devono persistere tra un deploy e l'altro, la documentazione indica la cache di fetch.
Dove sta la cache a runtime?
Di default in memoria, in uno store LRU dentro il processo del server. Quanto serva dipende da dove ospiti l'app. Su un server sempre acceso le voci restano tra una richiesta e l'altra, e cacheMaxMemorySize ne limita la dimensione. Su serverless ogni richiesta può finire su un'istanza diversa, quindi le voci create a runtime spesso si perdono; la cache creata in build continua ad alimentare la shell statica. Abbiamo confrontato questi compromessi in Next.js self-hosted con Docker o Vercel.
Due varianti della direttiva coprono i casi scoperti. 'use cache: remote' salva le voci in un cache handler fornito dalla piattaforma, come Redis o uno store KV, e funziona anche dopo aver letto i dati della richiesta. Costa un round trip di rete a ogni lettura e di solito una tariffa della piattaforma. 'use cache: private' può leggere cookie e header e tiene i risultati solo nella memoria del browser, mai sul server. La documentazione lo riserva a esigenze di compliance o a codice che non si può rifattorizzare per passare i dati della richiesta come argomento.
Cache Components richiede anche il runtime Node.js. Una route che esporta ancora runtime = 'edge' va spostata prima di attivare il flag, e questo chiude una delle domande del nostro confronto tra edge runtime e Node runtime per ogni progetto App Router che adotta il modello.
Che rapporto c'è tra cache e Partial Prerendering?
Sono lo stesso meccanismo visto da due lati. In build Next.js renderizza tutto quello che può: il markup statico e ogni scope in cache con una durata abbastanza lunga. Quell'output diventa la shell servita dalla CDN. I dati non in cache e le API legate alla richiesta stanno dentro boundary Suspense e arrivano in streaming nella stessa risposta. Scegliere un cacheLife vuol dire scegliere quanta parte della pagina arriva subito. Il lato delle route è nella nostra guida su come strutturare le route per il Partial Prerendering.
Le regole che seguiamo su una codebase SaaS
- In cache le funzioni di dati, non le pagine. Un
'use cache'in testa a un file di pagina mette in cache tutto quello che c'è sotto, compreso ciò che qualcuno aggiungerà il trimestre prossimo. UngetProject(id)in cache resta stretto quanto il suo nome. cacheLifein ogni scope. Le cache annidate senza una durata esplicita ereditano quella breve di una cache interna; in quel caso Next.js lancia un errore durante il prerender, e una durata dichiarata lo evita in partenza.- Tag per collezione e per record. Due tag per voce coprono la lista e la pagina di dettaglio.
updateTagnelle action,revalidateTag(tag, 'max')nei webhook. Chi ha fatto la modifica la vede subito; tutti gli altri la vedono alla richiesta successiva. È la stessa divisione che usiamo nei nostri pattern per le Server Action.- I dati della richiesta restano fuori. Il cookie si legge nella pagina, e l'ID del team si passa alla funzione in cache.
'use cache: remote'solo quando l'origine non regge il carico. Ogni lettura diventa una chiamata di rete: si accetta più latenza in cambio di meno query al database.
Quando il modello costa più di quanto fa risparmiare
Attivare cacheComponents su un'app esistente è una migrazione. Tutto diventa dinamico finché non lo marchi, quindi un sito che contava sui vecchi default prima rallenta e poi accelera. La validazione in build segnala ogni route che legge dati della richiesta fuori da un boundary Suspense, e su una codebase App Router grande risolverli è un lavoro vero, da fare route per route. Un'app interamente personale, senza dati condivisi, ci guadagna poco: la cache non ha niente da condividere tra le richieste. Per un sito marketing con contenuti che cambiano ogni settimana, invece, il modello calza bene: cacheLife('days') sulle funzioni dei contenuti, un tag per voce e un webhook che chiama revalidateTag a ogni pubblicazione.
Domande frequenti
Con l'handler di default no. Le voci vivono nella memoria di ogni istanza, e su serverless una richiesta può finire su un'istanza qualsiasi, quindi le voci create a runtime spesso si perdono tra una richiesta e l'altra. L'output messo in cache in build arriva comunque a ogni visitatore attraverso la shell statica. Per condividere le voci tra le istanze serve 'use cache: remote' con un cache handler come Redis o uno store KV, mettendo in conto un round trip di rete a ogni lettura.
Sì, se passi l'identificativo dell'utente come argomento. Leggi il cookie o la sessione fuori dalla funzione in cache e poi chiami qualcosa come getDashboard(userId); l'argomento entra nella chiave di cache, quindi ogni utente ha la sua voce. Non leggere mai cookies() dentro lo scope in cache: genera un errore. Se il codice non si può rifattorizzare così, 'use cache: private' mette in cache per utente solo nella memoria del browser, mai sul server.
No. unstable_cache continua a funzionare con Cache Components, quindi puoi lasciarlo e migrarlo più avanti. Le fetch che contavano su cache: 'force-cache' o su next.revalidate richiedono invece una scelta, perché con il flag attivo fetch non va in cache a meno che non giri dentro uno scope 'use cache'. Di solito si procede una funzione di dati alla volta: la si avvolge in 'use cache', il valore di revalidate passa a cacheLife e i tag passano a cacheTag. Ricorda che le voci di 'use cache' si azzerano a ogni deploy, mentre la cache di fetch può sopravvivere: conta molto con API esterne lente.
updateTag dentro una Server Action, quando chi ha fatto la modifica deve vederla nella schermata successiva, come al salvataggio di un form. Fa scadere subito le voci e la lettura successiva aspetta i dati freschi. revalidateTag(tag, 'max') quando qualche secondo di contenuto vecchio è accettabile, o quando non sei in una Server Action, come in un webhook dentro una Route Handler: i visitatori continuano a ricevere la copia in cache mentre se ne prepara una fresca. Chiamare revalidateTag con un solo argomento è deprecato e si comporta come una scadenza immediata.
Servizi correlati
Articoli correlati
Web
WebReact Server Components in produzione: 6 errori da evitare nel 2026
Il 45% degli sviluppatori React usa i Server Components, ma nello State of React 2025 sono la terza funzionalità meno gradita. Sei errori spiegano il divario.
Web