NAME
nextjs-16-cache-components — Next.js 16, Cache Components e React Compiler: cosa cambia davvero
SYNOPSIS
cat nextjs-16-cache-components.md
DESCRIPTION
Per anni una delle domande più fastidiose in Next.js è stata: "Questa pagina è statica o dinamica?". Sembra una domanda semplice, finché non aggiungi una chiamata a cookies(), un fetch con opzioni diverse, un client database, un CMS, un carrello o un pezzetto di contenuto personalizzato.
Next.js 16 è interessante perché prova a rendere questa conversazione meno misteriosa. Non elimina la complessità, ma sposta il modello mentale: le route sono dinamiche di default, la cache si dichiara dove serve, e Suspense diventa il modo naturale per comporre shell veloci con parti che restano fresche.
La feature da capire è Cache Components. Turbopack stabile, React Compiler, proxy.ts e le nuove API di invalidazione sono importanti, ma girano attorno allo stesso problema: costruire app veloci senza dover indovinare cosa il framework ha deciso dietro le quinte.
Perché questa cosa conta
In un'app reale non hai solo "pagine statiche" e "pagine dinamiche". Hai pezzi diversi con esigenze diverse.
La scheda prodotto può cambiare poche volte al giorno. Il prezzo magari cambia più spesso. La disponibilità deve essere quasi live. Il nome utente è personale. Le recensioni possono arrivare in streaming. La sidebar può essere stabile. Il carrello no.
Se tratti tutto come una sola unità, finisci sempre in uno dei due estremi:
- cache aggressiva e rischio di vedere dati vecchi;
- rendering dinamico ovunque e performance peggiore del necessario.
Cache Components serve proprio a evitare questa scelta finta.
Il modello in pratica
Con cacheComponents: true, puoi dichiarare cosa è cacheabile usando "use cache". Poi puoi associare durata e tag con cacheLife() e cacheTag(). Le parti dinamiche restano dinamiche e possono essere isolate con Suspense.
La configurazione è piccola:
// next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { cacheComponents: true, }; export default nextConfig;
Il cambio grosso non è nella config. È nel modo in cui inizi a scrivere i componenti.
// app/products/[slug]/page.tsx import { Suspense } from 'react'; import { cacheLife, cacheTag } from 'next/cache'; async function getProduct(slug: string) { 'use cache'; cacheLife('hours'); cacheTag(`product:${slug}`); return db.product.findUnique({ where: { slug } }); } async function ProductDetails({ slug }: { slug: string }) { const product = await getProduct(slug); return ( <section> <h1>{product.name}</h1> <p>{product.description}</p> </section> ); } async function LiveInventory({ slug }: { slug: string }) { const inventory = await db.inventory.findFirst({ where: { slug } }); return <p>{inventory.quantity} pezzi disponibili</p>; } export default async function ProductPage({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; return ( <> <ProductDetails slug={slug} /> <Suspense fallback={<p>Controllo disponibilità...</p>}> <LiveInventory slug={slug} /> </Suspense> </> ); }
La pagina non deve essere tutta cacheata o tutta dinamica. La scheda prodotto può essere veloce e riusabile. L'inventario può restare fresco. L'utente vede qualcosa subito, senza aspettare la parte più lenta.
use cache è documentazione eseguibile
La cosa che mi piace di "use cache" è che obbliga a rendere esplicita un'intenzione. Quando leggi una funzione, capisci subito che qualcuno ha deciso: "questo dato può essere riusato".
È particolarmente utile quando non stai usando fetch. Tante app leggono dati da Prisma, Drizzle, SDK interni, client CMS o funzioni di servizio. In quei casi il vecchio ragionamento basato solo sulle opzioni di fetch non bastava.
Una regola pratica:
- cachea contenuto relativamente stabile;
- usa tag granulari;
- lascia dinamici permessi, sessioni, carrelli, notifiche e stati transazionali;
- metti le parti lente dentro
Suspense; - misura prima di dire "abbiamo migliorato la performance".
Invalidare senza buttare via tutto
La cache è utile solo se puoi aggiornarla con precisione. Qui cacheTag, revalidateTag e updateTag diventano importanti.
Esempio:
'use server'; import { updateTag } from 'next/cache'; export async function updateProductName(productId: string, name: string) { await db.product.update({ where: { id: productId }, data: { name }, }); updateTag(`product:${productId}`); }
Il dettaglio importante è il tag. product:${productId} racconta un confine preciso. products racconta un secchio enorme. All'inizio il secchio enorme è comodo; dopo qualche mese diventa il motivo per cui invalidi mezza app per cambiare un titolo.
Turbopack stabile: la parte che senti ogni giorno
Next.js 16 porta Turbopack al centro per sviluppo e build. Non è la feature più poetica, ma è quella che senti mentre lavori: server che parte prima, refresh più rapido, build che smettono di sembrare una pausa caffè forzata.
Detto questo, non migrerei una codebase piena di plugin custom a occhi chiusi. Controllerei:
- build locale;
- moduli non standard;
- MDX, SVG e CSS;
- plugin Webpack rimasti;
- pagine critiche;
- differenze nei tempi di build.
Per i progetti nuovi, partirei dal default. Per quelli maturi, farei una migrazione misurata.
React Compiler: togliere rumore, non pensiero
React Compiler 1.0 è stabile e Next.js 16 lo supporta con reactCompiler. La promessa è ridurre molta memoization manuale: meno memo, meno useMemo, meno useCallback usati "per sicurezza".
// next.config.ts import type { NextConfig } from 'next'; const nextConfig: NextConfig = { reactCompiler: true, }; export default nextConfig;
Non lo tratterei come una polvere magica. Il compiler aiuta quando il codice segue bene le regole di React. Se i componenti hanno side effect strani, mutazioni nascoste o hook usati male, prima va sistemato quello.
Il modo sano di provarlo:
- aggiorna
eslint-plugin-react-hooks; - correggi le violazioni reali;
- abilitalo su un'area controllata;
- misura build time e comportamento;
- rimuovi memoization manuale solo quando non serve più.
L'obiettivo non è cancellare ogni useMemo. L'obiettivo è smettere di scrivere memoization preventiva perché abbiamo paura del render.
proxy.ts e il confine di rete
Il vecchio middleware.ts diventa proxy.ts. È un cambio di nome, ma ha senso: quel file sta al confine della richiesta, non è un middleware generico in stile backend tradizionale.
// proxy.ts import { NextRequest, NextResponse } from 'next/server'; export default function proxy(request: NextRequest) { const isLoggedIn = Boolean(request.cookies.get('session')); if (!isLoggedIn && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); }
La regola qui è semplice: tienilo piccolo. Redirect, auth routing, header, rewrites essenziali. Se inizia a sembrare un secondo backend, probabilmente sta facendo troppo.
Come migrerei davvero
Non accenderei tutte le feature insieme. Farei così:
- aggiorno Next, React e React DOM;
- lancio il codemod ufficiale;
- sistemo breaking change su
params,searchParams,cookies(),headers()edraftMode(); - migro
middleware.tsaproxy.ts; - verifico build e pagine critiche;
- abilito Cache Components su una sezione dove oggi la cache crea attrito;
- definisco convenzioni per tag e invalidazione;
- provo React Compiler separatamente;
- confronto metriche prima e dopo.
La migrazione buona non è quella che usa tutte le feature nuove. È quella che rende il comportamento dell'app più leggibile.
Cosa cambia nel modo di pensare
La cosa più utile di Next.js 16 è che ti costringe a nominare meglio le intenzioni. Una funzione non è solo "prende il prodotto dal database". È "prende il prodotto, lo posso cacheare per ore, lo invalido con questo tag". Un componente non è solo "renderizza la pagina". È "questa è la shell veloce, questo pezzo è personale, questo arriva in streaming".
All'inizio sembra più lavoro. Poi diventa una forma di calma. Le decisioni di performance non stanno più nascoste in una combinazione di default, euristiche e memoria tribale. Stanno nel codice.
Fonti utili
METADATA
- date: 2026-05-24
- reading: 6 min
- author: Filippo Spinella
- tags: Next.js, React, Frontend, Performance