Per molto tempo la performance è stata l'ultima fase del progetto. L'applicazione è stata costruita, misurata alla fine, e quando la pagina era lenta, sono usciti i palliativi: una cache qui, uno spinner lì, un lazy load là. Era un dettaglio finale, gestito dopo che le decisioni importanti erano già state prese.
Modern Next.js smantella questo ordine. Cache, streaming e Suspense non sono pulsanti da attivare alla fine; sono proprietà di come la pagina viene assemblata e consegnata. Decidere quando ricalcolare i dati, in quale ordine appaiono le parti dello schermo e cosa può essere servito prima del resto sono scelte strutturali. La tesi di questo testo è semplice: la prestazione percepita è diventata una decisione architettonica, e trattarla come un dettaglio finale è diventato costoso.
Tre meccanismi che risolvono problemi diversi
Vale la pena separare ciò che fa ogni pezzo, perché tendono a essere confusi con un'unica vaga idea di "partire velocemente".
La cache riguarda il non rifare il lavoro. Se i dati sono già stati recuperati o una pagina è già stata renderizzata, salvandola si evita di pagare nuovamente lo stesso costo alla richiesta successiva. Il guadagno sta nella produttività e nella latenza: le risposte che non hanno bisogno di toccare la banca vengono emesse in frazioni di tempo.
Lo streaming significa non aspettare che tutto sia pronto per iniziare a consegnare. Invece di trattenere l'intera pagina finché non termina la parte più lenta, il server invia l'HTML in blocchi non appena ciascuno diventa disponibile. L'utente inizia a vedere e interagire con ciò che è già arrivato mentre il resto è ancora in fase di assemblaggio.
La suspense è ciò che rende utilizzabile lo streaming. Ti permette di dichiarare, nel componente stesso, "mentre questi dati non sono sufficienti, mostra questo". Segna i confini tra ciò che è pronto e ciò che è ancora in caricamento, dando al framework il permesso di inviare lo schermo in blocchi coerenti invece che in un singolo blocco.
Come lavorano insieme
La magia appare nella combinazione. Immagina una pagina di prodotto: intestazione, dati dell'articolo, recensioni e consigli. L'intestazione e i dati dell'articolo sono veloci. Le valutazioni si basano su un’aggregazione pesante. Le raccomandazioni richiedono un servizio esterno lento.
Nel vecchio modello, l'intera pagina attendeva il componente più lento. L'utente ha guardato uno schermo vuoto fino a quando tutto, inclusa la raccomandazione proveniente da una terza persona scontrosa, non è stata completata. Le prestazioni della pagina erano ostaggio del suo elemento peggiore.
Con la Suspense che delimita ogni blocco e lo streaming attivo, il server fornisce istantaneamente l'intestazione e i dati dell'elemento, con indicatori di caricamento al posto di valutazioni e consigli. Quando ogni parte è pronta sul server, viene trasmessa e scatta in posizione. Sotto, la cache garantisce che alla visita successiva le parti che non sono cambiate non debbano nemmeno essere ricalcolate. I tre meccanismi si sommano: il caching riduce il lavoro, lo streaming elimina l'attesa peggiore, la Suspense organizza la consegna.
Il risultato è che la prestazione percepita si ferma a seconda del componente più lento e inizia a seconda di come hai tracciato i confini. E tracciare i confini è architettura.
Perché questa diventa una decisione architettonica e non un aggiustamento finale
Tieni presente che ciascuna scelta di cui sopra è stata effettuata all'inizio, non alla fine. Dove posizionare un limite di suspense, quali dati possono attendere e cosa deve essere nel primo byte, cosa è memorizzabile nella cache e per quanto tempo: tutto ciò modella la struttura dei componenti e il modo in cui i dati vengono recuperati.
Non puoi "aggiungere lo streaming in seguito" su una pagina scritta come un blocco monolitico che recupera tutto in una volta. Per eseguire lo streaming, la pagina deve essere stata progettata in parti indipendenti, ciascuna con il proprio limite di caricamento. Questa scomposizione è una decisione di progettazione che avviene all'inizio, insieme alla modellazione dei dati.
Lo stesso vale per la cache. Decidere cosa può essere servito da una versione salvata e cosa deve essere sempre aggiornato significa, in pratica, classificare i dati del tuo dominio in base alla tolleranza obsoleta. Questa non è una modifica delle prestazioni, è una dichiarazione sull'azienda: questo prezzo potrebbe essere vecchio di qualche minuto? Questo equilibrio, no? Queste risposte appartengono all'architettura, e chi le rimanda fino alla fine scopre che riscrivere la struttura di ricerca dei dati è molto più costoso che averci pensato prima. La relazione con il resto del framework è più chiara nella guida Next.js App Router.
Il rischio che nessuno menziona nella bellissima slide: cache di malintesi
La cache è la parte più seducente e più pericolosa. La frase classica secondo cui l'invalidazione della cache è uno dei problemi più difficili dell'informatica non è uno scherzo del programmatore, è una descrizione della produzione.
Il problema centrale sono i dati vecchi. Nel momento in cui decidi di salvare una risposta, accetti che potrebbe non essere aggiornata quando qualcuno la leggerà di nuovo. Per contenuti che cambiano lentamente, fantastico. Per un saldo, una giacenza, uno stato d'ordine, servire una versione salvata da troppo tempo significa mostrare all'utente una realtà che non esiste più. E il tipo peggiore di questo bug è quello silenzioso: niente si rompe, niente dà errori, lo schermo semplicemente mente.
Next.js offre controlli precisi della memorizzazione nella cache, proprio perché queste decisioni devono essere basate sui dati, non globali. Ma il controllo accurato è un'arma a doppio taglio: lo sviluppatore che non capisce esattamente in quale livello vengono archiviati i dati effettuerà il debug di comportamenti fantasma. Perché questa pagina non si aggiorna? Perché c'è un livello di cache di cui aveva dimenticato l'esistenza, con una chiave di invalidazione che nessuno ha attivato. Chiunque voglia approfondire troverà una buona panoramica in buone pratiche di memorizzazione nella cache nelle applicazioni.
La complessità fa parte dell'accordo
C’è un costo cognitivo che deve essere onestamente incluso. Con più livelli di memorizzazione nella cache, streaming e limiti di suspense, il modello mentale di "cosa succede quando l'utente richiede questa pagina" diventa più ricco e più difficile da tenere in testa.
I dati possono essere archiviati a più livelli, con durate diverse. Una parte della pagina viene renderizzata sul server e trasmessa in streaming, un'altra si idrata sul client. Quando qualcosa sembra obsoleto, l’indagine deve passare attraverso questi livelli per scoprire dove si è bloccata la vecchia versione. Ciò richiede che il team comprenda il modello e non si limiti a copiare le configurazioni da un esempio su Internet.
Pertanto, la raccomandazione pratica è di essere espliciti e conservativi. Inizia con meno cache di quanto sembri allettante e aggiungi livelli come richiesto dalla misurazione, documentando la strategia di invalidazione per ciascuno. Tratta la memorizzazione nella cache aggressiva dei dati sensibili come una decisione che necessita di giustificazione, non come un'impostazione predefinita. La regola d’oro è che nessuno dovrebbe essere in grado di spiegare perché uno schermo mostra vecchie informazioni semplicemente alzando le spalle.
Cosa prendere in considerazione nella decisione
Cache, streaming e Suspense, insieme, hanno reso le prestazioni percepite molto migliori di quanto fosse possibile con i trucchi finali della generazione precedente. Il guadagno è concreto: pagine che appaiono a pezzi, lavoro che non si ripete, attese che non rallentano l'utente nel peggiore dei casi.
Il contrappunto è che questi vantaggi derivano da decisioni prese in anticipo, sui limiti dei componenti e sulla tolleranza per i dati obsoleti. Rimandarli fino alla fine non è neutrale: significa rinunciare allo streaming e spingere la cache nel territorio dei vecchi dati silenziosi. La prestazione percepita è diventata architettura, con tutto ciò che ciò implica di disciplina progettuale e di invalidazione.
Se stai definendo lo stack di un'applicazione Next.js e vuoi evitare l'incubo della cache che nessuno capisce, vale la pena progettare la strategia dei dati prima della prima schermata. Ho discusso molto di questo progetto; Trovami nei commenti o sulle reti per scambiare idee.
Leggi anche
- Server-First: la decisione architettonica di alleggerire il browser
- Router per app Next.js: la guida al Thinking Server per impostazione predefinita
- Azioni del server in Next.js: mutazioni senza mantenere un'API solo per quello
- Cosa sono i componenti del server React e perché Logic sta tornando al server
- Cache nelle applicazioni
- Cache nelle applicazioni: buone pratiche e principi fondamentali
