Web Design and Engineering30 luglio 20267 min di lettura

Partial Prerendering stabile in Next.js 16: come strutturare le route

Next.js 16 rende stabile Partial Prerendering con cacheComponents: shell statica dalla CDN edge, parti dinamiche in streaming nella stessa risposta.

a very tall building with lots of scaffolding around it

Partial Prerendering è un modello di rendering di Next.js che serve una route come shell statica e trasmette in streaming le sue parti dinamiche nella stessa risposta HTTP. La shell (layout, navigazione, header, tutto ciò che è identico per ogni visitatore) parte dalla CDN edge in pochi millisecondi. Le parti personalizzate (una riga della dashboard, il totale di un carrello, un feature flag) vengono generate a request time e riempiono i placeholder che React ha già disegnato.

È diventato stabile in Next.js 16, uscito a ottobre 2025. Il vecchio flag experimental.ppr non c'è più. Ora si attiva tramite un modello più ampio chiamato Cache Components. Se costruisci un SaaS sull'App Router, questo cambia il modo in cui decidi cosa è statico e cosa è dinamico, route per route. Prima la scelta era tutto-o-niente per pagina. Ora è per component.

La versione in 30 secondi

Prima di PPR, una route di Next.js era statica oppure dinamica. Una chiamata a cookies(), un fetch non in cache, e l'intera pagina diventava dinamica: ogni visitatore aspettava il server, la CDN non metteva niente in cache e il time to first byte seguiva la query più lenta. PPR rompe questo compromesso. La shell statica viene pre-renderizzata a build time e servita dall'edge. Il lavoro dinamico avviene solo dentro i boundary Suspense, e solo quelle parti aspettano. Il lettore vede subito il layout e guarda i buchi dinamici riempirsi.

Perché ci sono volute tre versioni?

PPR è arrivato come flag sperimentale in Next.js 14, è stato rifinito nella 15 ed è diventato stabile nella 16. Il ritardo non era marketing. La parte difficile è la correttezza: Next.js deve dimostrare, a build time, quali parti di un albero di component sono davvero statiche e quali dipendono dai dati della richiesta, poi serializzare l'output statico più un blob "postponed" che permette al server di riprendere il rendering delle parti dinamiche a ogni richiesta. Sbaglia quel confine e o mischi i dati di un utente nella cache di un altro, o rendi tutto dinamico e perdi il senso dell'operazione.

Next.js 16 ha risolto l'ergonomia ribaltando il default. Con Cache Components tutto è dinamico se non scegli il contrario. Segni ciò che va messo in cache con la direttiva 'use cache' e segni ciò che deve restare dinamico avvolgendolo in Suspense. Il framework smette di indovinare.

Come funziona davvero Partial Prerendering?

Tre fasi, e conviene tenerle distinte.

Build time. Next.js renderizza le parti della route che non dipendono dai dati della richiesta: il layout, il testo statico e la UI di fallback di ogni boundary Suspense. Salva tutto come shell HTML statica più un blob serializzato del rendering in pausa.

Request time. La shell parte per prima, direttamente dalla CDN edge. Dato che sono gli stessi byte per tutti, la latenza dell'edge (circa 20-50ms) è l'intero costo. Il server poi riprende il rendering in pausa, produce le sezioni dinamiche e le trasmette sulla stessa connessione con chunked transfer encoding. Nessuna seconda richiesta.

Hydration. React idrata ogni boundary Suspense sul posto, man mano che arriva il suo chunk. L'utente legge la shell mentre i buchi stanno ancora caricando, ed è tutto qui l'argomento sulle prestazioni.

Vale la pena leggere il meccanismo per intero nell'articolo di un ingegnere del core di Next.js, linkato tra le fonti. Per l'architettura conta la regola che ne deriva: un component è dinamico se, e solo se, legge dati a request time (cookies(), headers(), searchParams o un fetch non in cache) e sta fuori da un boundary in cache.

Cosa ci guadagni, alla prova dei numeri

Il guadagno concreto è il time to first byte. Quando l'intera route è dinamica, il TTFB aspetta l'origin e il database. Con PPR la shell è statica, quindi il TTFB equivale alla distanza tra l'utente e il nodo edge più vicino, non alla velocità della query più lenta. Un resoconto di produzione ha misurato una route passare da 850ms a meno di 100ms di TTFB dopo aver reso statica la shell (fonte più sotto). I numeri che vedrai saranno diversi. Il meccanismo no: statico-dall'edge batte dinamico-dall'origin sul first byte ogni volta, e il first byte è ciò che misurano sia i Core Web Vitals sia gli utenti senza pazienza. Il budget più ampio l'abbiamo trattato nel nostro playbook sui Core Web Vitals.

Come strutturare le route di un SaaS intorno a PPR

È qui che il modello si guadagna il posto. Una pagina SaaS raramente è tutta statica o tutta dinamica. È una cornice statica intorno a poche celle personalizzate. Struttura la route di conseguenza.

Spingi il confine dinamico più a fondo che puoi. Non avvolgere tutto il corpo della pagina in un solo Suspense. Avvolgi il sottoalbero più piccolo che legge davvero i dati della richiesta. La shell di una dashboard, la sidebar e la griglia di card vuote possono essere statiche; solo i numeri dentro ogni card sono dinamici. Più albero tieni nella shell statica, più pagina appare all'istante.

Metti in cache il condiviso, personalizza il resto. Pagine marketing, documentazione, prezzi e la superficie da utente sloggato sono shell statiche con quasi zero contenuto dinamico. Le route dell'app fanno il contrario: cornice statica intorno a celle dinamiche. Abbiamo già sostenuto che il sito marketing e l'app vanno trattati come due architetture diverse. PPR permette a un solo framework di servirle entrambe senza cambiare modalità di rendering.

Dai a ogni buco un fallback vero. Il fallback di Suspense non è una tassa sullo spinner. Fa parte della shell statica, quindi parte subito e definisce il layout. Usa skeleton che rispettano la dimensione finale del contenuto, altrimenti la shell si riassesta quando arrivano i dati e fallisci il Cumulative Layout Shift.

Attento a cosa metti nel layout. Una sola lettura di cookies() in un layout radice, fuori da un boundary, riporta l'intera route al rendering dinamico. È il modo più comune in cui i team si escludono da PPR senza accorgersene. Leggi auth e locale dentro un component delimitato, non in cima.

Tieni accesa l'osservabilità dello streaming. PPR rende solo se riesci a vedere le parti dinamiche arrivare. Misura il TTFB separato dal caricamento completo, e guarda la coda: un buco dinamico lento non blocca più la shell, ma blocca comunque i dati per cui l'utente è venuto.

Quando non usarlo

PPR non è complessità gratis. Saltalo quando l'intera route è davvero dinamica, un feed personalizzato dove niente è condiviso, perché non c'è nessuna shell statica da estrarre e aggiungi configurazione senza guadagno. Saltalo per contenuti del tutto statici (un articolo di blog, una pagina di documentazione) che erano già veloci senza. E tratta l'opt-in come una migrazione, non come un interruttore: attivare cacheComponents rende tutto dinamico di default, quindi un'app esistente ha bisogno che ogni superficie in cache sia marcata con 'use cache' prima di comportarsi come prima. Su un codebase App Router grande è lavoro vero, fatto route per route.

In breve: PPR trasforma "statico o dinamico" da verdetto per pagina a decisione per component. Per un SaaS, dove quasi ogni pagina è una cornice statica intorno a un po' di dati vivi, è il modello di rendering che l'App Router avrebbe dovuto avere dall'inizio. In Next.js 16 arriva finalmente come strada di default, non come esperimento.

Foto di Paul Becker su Unsplash

Domande frequenti

Partial Prerendering è stabile in Next.js 16?

Sì. PPR è diventato stabile in Next.js 16 (ottobre 2025). Il flag experimental.ppr e la config di segmento experimental_ppr sono stati rimossi. Ora lo attivi tramite Cache Components, impostando cacheComponents: true in next.config.ts, che sblocca anche la direttiva use cache. Le versioni precedenti (14 e 15) lo tenevano dietro un flag sperimentale, quindi il codice scritto per quelle ha bisogno dell aggiornamento di config.

Che differenza c è tra Partial Prerendering e streaming SSR?

Lo streaming SSR renderizza tutta la pagina sul server a ogni richiesta e la trasmette man mano, quindi il first byte aspetta comunque il server. PPR pre-renderizza una shell statica a build time e la serve dalla CDN edge, poi trasmette solo i buchi dinamici dall origin. Così PPR ti dà layout istantaneo per ogni visitatore più lo streaming per le parti che ne hanno bisogno. Usa Suspense come lo streaming, ma la shell intorno a quei boundary è già costruita.

Devo riscrivere l app per attivare Partial Prerendering?

Non riscriverla, ma migrarla. Attivare cacheComponents ribalta il default: ogni route diventa dinamica se non marchi i dati in cache con use cache e non delimiti le letture dinamiche in Suspense. Su un app piccola è un pomeriggio. Su un codebase App Router grande, fallo route per route e sta attento alle letture a request time (cookies, headers) nei layout, perché una di quelle esclude l intera route dalla shell statica.

Partial Prerendering ha bisogno di Vercel?

No. PPR è una funzione di Next.js, non solo di Vercel. La shell statica è normale HTML pre-renderizzato e le parti dinamiche viaggiano su una normale risposta HTTP in chunk, quindi qualsiasi host che esegue Next.js 16 può servirla. Il beneficio pieno sul TTFB arriva quando una CDN sta davanti e serve la shell statica dall edge, cosa che imposti su Cloudflare, una CDN self-hosted o il tuo reverse proxy. Senza cache edge la shell parte comunque per prima, solo dal tuo origin.

Articoli correlati

Studio

Inizia un progetto.

Un partner unico per tutto il progetto. Produzione più veloce, tecnologie moderne, costi ridotti.