Rendering sul Web

Pubblicato il 6 febbraio 2019, ultimo aggiornamento il 5 gennaio 2026

Una delle decisioni fondamentali che gli sviluppatori web devono prendere è dove implementare la logica e il rendering nella loro applicazione. Può essere difficile perché esistono molti modi per creare un sito web.

La nostra comprensione di questo spazio è basata sul nostro lavoro in Chrome con i siti di grandi dimensioni negli ultimi anni. In generale, incoraggiamo gli sviluppatori a prendere in considerazione il rendering lato server o il rendering statico rispetto a un approccio di reidratazione completa.

Per comprendere meglio le architetture tra cui scegliere quando prendiamo questa decisione, abbiamo bisogno di una terminologia coerente e di un framework condiviso per ogni approccio. In seguito, puoi valutare meglio i compromessi di ogni approccio di rendering dal punto di vista del rendimento della pagina.

Terminologia

Innanzitutto, definiamo alcuni termini che utilizzeremo.

Rendering

Rendering lato server (SSR)
Esecuzione del rendering di un'app sul server per inviare HTML, anziché JavaScript, al client.
Rendering lato client (CSR)
Esecuzione del rendering di un'app in un browser, utilizzando JavaScript per modificare il DOM.
Prerendering
Esecuzione di un'applicazione lato client in fase di build per acquisire il suo stato iniziale come HTML statico. Nota: il termine "prerendering" in questo senso è diverso dal prerendering del browser delle navigazioni future.
Idratazione
Esecuzione di script lato client per aggiungere stato dell'applicazione e interattività all' HTML sottoposto a rendering lato server. L'idratazione presuppone che il DOM non cambi.
Reidratazione
Sebbene spesso utilizzato con lo stesso significato di idratazione, il termine reidratazione implica l'aggiornamento regolare del DOM con lo stato più recente, anche dopo l'idratazione iniziale.

Rendimento

Time to First Byte (TTFB)
Il tempo che intercorre tra il clic su un link e il primo byte di contenuti caricati nella nuova pagina.
First Contentful Paint (FCP)
Il momento in cui i contenuti richiesti (corpo dell'articolo e così via) diventano visibili.
Interaction to Next Paint (INP)
Una metrica rappresentativa che valuta se una pagina risponde costantemente e rapidamente agli input dell'utente.
Total Blocking Time (TBT)
Una metrica proxy per l'INP che calcola per quanto tempo il thread principale è stato bloccato durante il caricamento della pagina.

Rendering lato server

Il rendering lato server genera l'HTML completo per una pagina sul server in risposta alla navigazione. In questo modo si evitano ulteriori round trip per il recupero dei dati e la creazione di modelli sul client, perché il renderer li gestisce prima che il browser riceva una risposta.

Il rendering lato server in genere produce un FCP veloce. L'esecuzione della logica e del rendering della pagina sul server consente di evitare di inviare molti JavaScript al client. Ciò contribuisce a ridurre il TBT di una pagina, che può anche portare a un INP inferiore, perché il thread principale non viene bloccato così spesso durante il caricamento della pagina. Quando il thread principale viene bloccato meno spesso, le interazioni dell'utente hanno maggiori opportunità di essere eseguite prima.

Questo ha senso, perché con il rendering lato server invii solo testo e link al browser dell'utente. Questo approccio può funzionare bene per una varietà di condizioni di dispositivo e di rete e apre interessanti ottimizzazioni del browser, come l'analisi dei documenti in streaming.

Diagramma che mostra il rendering lato server e l'esecuzione di JavaScript che influiscono su FCP e TTI.
FCP e TTI con rendering lato server.

Con il rendering lato server, è meno probabile che gli utenti debbano attendere l'esecuzione di JavaScript con vincoli di CPU prima di poter utilizzare il tuo sito. Anche quando non puoi evitare JavaScript di terze parti, l'utilizzo del rendering lato server per ridurre i costi di JavaScript proprietario può darti più budget per il resto. Tuttavia, esiste un potenziale compromesso con questo approccio: la generazione di pagine sul server richiede tempo, il che può aumentare il TTFB della pagina.

Se il rendering lato server è sufficiente per la tua applicazione dipende in gran parte da che tipo di esperienza stai creando. Esiste un dibattito di lunga data sulle applicazioni corrette del rendering lato server rispetto al rendering lato client, ma puoi sempre scegliere di utilizzare il rendering lato server per alcune pagine e non per altre. Alcuni siti hanno adottato con successo tecniche di rendering ibrido. Ad esempio, Netflix esegue il rendering lato server delle pagine di destinazione relativamente statiche, mentre prefetching JavaScript per le pagine con molte interazioni, offrendo a queste pagine con rendering lato client più pesanti una maggiore probabilità di caricarsi rapidamente.

Con molti framework, librerie e architetture moderni, puoi eseguire il rendering della stessa applicazione sia sul client sia sul server. Puoi utilizzare queste tecniche per il rendering lato server. Tuttavia, le architetture in cui il rendering avviene sia sul server sia sul client sono una classe di soluzioni a sé stante con caratteristiche di rendimento e compromessi molto diversi. Gli utenti di React possono utilizzare le API DOM del server o soluzioni basate su di esse come Next.js per il rendering lato server. Gli utenti di Vue possono utilizzare la guida al rendering lato server di Vue o Nuxt. Angular ha Universal.

La maggior parte delle soluzioni più diffuse utilizza una forma di idratazione, quindi tieni presente gli approcci utilizzati dal tuo strumento.

Rendering statico

Il rendering statico avviene in fase di build. Questo approccio offre un FCP veloce, nonché un TBT e INP inferiori, a condizione che tu limiti la quantità di JavaScript lato client nelle tue pagine. A differenza del rendering lato server, ottiene anche un TTFB costantemente veloce, perché l'HTML di una pagina non deve essere generato dinamicamente sul server. In genere, il rendering statico significa produrre in anticipo un file HTML separato per ogni URL. Con le risposte HTML generate in anticipo, puoi eseguire il deployment dei rendering statici su più CDN per sfruttare la memorizzazione nella cache edge.

Diagramma che mostra il rendering statico e l'esecuzione facoltativa di JavaScript che influiscono su FCP e TTI.
FCP e TTI con rendering statico.

Le soluzioni per il rendering statico sono disponibili in tutte le forme e dimensioni. Strumenti come Gatsby sono progettati per far sentire agli sviluppatori che la loro applicazione viene sottoposta a rendering dinamico, non generata come passaggio di build. Gli strumenti di generazione di siti statici come 11ty, Jekyll, e Metalsmith adottano la loro natura statica, fornendo un approccio più basato sui modelli.

Uno degli svantaggi del rendering statico è che deve generare singoli file HTML per ogni URL possibile. Questa operazione può essere difficile o addirittura impossibile quando devi prevedere questi URL in anticipo e per i siti con un numero elevato di pagine uniche.

Gli utenti di React potrebbero avere familiarità con Gatsby, l'esportazione statica di Next.js o Navi, che semplificano la creazione di pagine dai componenti. Tuttavia, il rendering statico e il prerendering si comportano in modo diverso: le pagine con rendering statico sono interattive senza dover eseguire molti JavaScript lato client, mentre il prerendering migliora l'FCP di un'applicazione a pagina singola che deve essere avviata sul client per rendere le pagine veramente interattive.

Se non sai se una determinata soluzione è il rendering statico o il prerendering, prova a disattivare JavaScript e a caricare la pagina che vuoi testare. Per le pagine con rendering statico, la maggior parte delle funzionalità interattive esiste ancora senza JavaScript. Le pagine con prerendering potrebbero avere ancora alcune funzionalità di base come i link con JavaScript disattivato, ma la maggior parte della pagina è inerte.

Un altro test utile consiste nell'utilizzare la limitazione della rete in Chrome DevTools e vedere quanti JavaScript vengono scaricati prima che una pagina diventi interattiva. Il prerendering in genere richiede più JavaScript per diventare interattivo e questo JavaScript tende a essere più complesso dell' approccio di miglioramento progressivo utilizzato nel rendering statico.

Rendering lato server rispetto al rendering statico

Il rendering lato server non è la soluzione migliore per tutto, perché la sua natura dinamica può comportare costi di overhead di calcolo significativi. Molte soluzioni di rendering lato server non eseguono il flush anticipato, ritardano il TTFB o raddoppiano i dati inviati (ad esempio, gli stati inlined utilizzati da JavaScript sul client). In React, renderToString() può essere lento perché è sincrono e a thread singolo. Le API DOM del server React più recenti supportano lo streaming, che può inviare la parte iniziale di una risposta HTML al browser prima che il resto venga generato sul server.

Per "fare bene" il rendering lato server potrebbe essere necessario trovare o creare una soluzione per la memorizzazione nella cache dei componenti, la gestione del consumo di memoria, l'utilizzo di tecniche di memoizzazione, e altre problematiche. Spesso elabori o ricompili la stessa app due volte, una sul client e una sul server. Il rendering lato server che mostra i contenuti prima non significa necessariamente che tu debba fare meno lavoro. Se hai molto lavoro sul client dopo che una risposta HTML generata dal server arriva sul client, questo può comunque portare a un TBT e un INP più elevati per il tuo sito web.

Il rendering lato server produce HTML on demand per ogni URL, ma può essere più lento della semplice pubblicazione di contenuti con rendering statico. Se puoi fare il lavoro aggiuntivo il rendering lato server più la memorizzazione nella cache HTML può ridurre significativamente il tempo di rendering del server. Il vantaggio del rendering lato server è la possibilità di recuperare più dati "live" e rispondere a un insieme più completo di richieste rispetto a quanto possibile con il rendering statico. Le pagine che richiedono la personalizzazione sono un esempio concreto del tipo di richiesta che non funziona bene con il rendering statico.

Il rendering lato server può anche presentare decisioni interessanti quando crei una PWA. È meglio utilizzare la memorizzazione nella cache del service worker a pagina intera o eseguire il rendering lato server di singoli contenuti?

Rendering lato client

Il rendering lato client significa eseguire il rendering delle pagine direttamente nel browser con JavaScript. Tutta la logica, il recupero dei dati, la creazione di modelli e il routing vengono gestiti sul client anziché sul server. Il risultato effettivo è che dal server vengono passati più dati al dispositivo dell'utente, il che comporta una serie di compromessi.

Il rendering lato client può essere difficile da rendere e mantenere veloce per i dispositivi mobili. Con un po' di lavoro per mantenere un budget JavaScript limitato e fornire valore nel minor numero possibile di round trip, puoi fare in modo che il rendering lato client replichi quasi il rendimento del rendering lato server puro. Puoi fare in modo che il parser funzioni più velocemente fornendo script e dati critici utilizzando <link rel=preload> Ti consigliamo inoltre di prendere in considerazione l'utilizzo di pattern come PRPL per garantire che le navigazioni iniziali e successive siano immediate.

Diagramma
    che mostra il rendering lato client che influisce su FCP e TTI.
FCP e TTI con rendering lato client.

Lo svantaggio principale del rendering lato client è che la quantità di JavaScript richiesta tende ad aumentare con la crescita di un'applicazione, il che può influire sull'INP di una pagina. Questo diventa particolarmente difficile con l'aggiunta di nuove librerie JavaScript, polyfill e codice di terze parti, che competono per la potenza di elaborazione e spesso devono essere elaborati prima che i contenuti di una pagina possano essere sottoposti a rendering.

Le esperienze che utilizzano il rendering lato client e si basano su bundle JavaScript di grandi dimensioni devono prendere in considerazione la suddivisione aggressiva del codice per ridurre il TBT e l'INP durante il caricamento della pagina, nonché il caricamento lento di JavaScript per pubblicare solo ciò di cui l'utente ha bisogno, quando ne ha bisogno. Per le esperienze con poca o nessuna interattività, il rendering lato server può rappresentare una soluzione più scalabile a questi problemi.

Per chi crea applicazioni a pagina singola, l'identificazione delle parti principali dell'interfaccia utente condivise dalla maggior parte delle pagine consente di applicare la tecnica di memorizzazione nella cache dell'applicazione shell. In combinazione con i service worker, questa tecnica può migliorare notevolmente il rendimento percepito nelle visite ripetute, perché la pagina può caricare l'HTML e le dipendenze dell'applicazione shell da CacheStorage molto rapidamente.

La reidratazione combina il rendering lato server e lato client

L'idratazione è un approccio che mitiga i compromessi tra il rendering lato client e lato server eseguendoli entrambi. Le richieste di navigazione, come i caricamenti o i ricaricamenti di pagine intere, vengono gestite da un server che esegue il rendering dell'applicazione in HTML. Poi, il JavaScript e i dati utilizzati per il rendering vengono incorporati nel documento risultante. Se eseguita con attenzione, questa tecnica ottiene un FCP veloce come il rendering lato server, poi "riprende" eseguendo di nuovo il rendering sul client.

Questa è una soluzione efficace, ma può presentare notevoli svantaggi in termini di rendimento.

Lo svantaggio principale del rendering lato server con reidratazione è che può avere un impatto negativo significativo su TBT e INP, anche se migliora l'FCP. Le pagine con rendering lato server possono sembrare caricate e interattive, ma non possono rispondere all'input finché non vengono eseguiti gli script lato client per i componenti e non vengono collegati i gestori di eventi. Sui dispositivi mobili, questa operazione può richiedere minuti, creando confusione e frustrazione per l'utente.

Un problema di reidratazione: un'app al prezzo di due

Affinché JavaScript lato client possa subentrare con precisione nel punto in cui il server ha interrotto l'esecuzione, senza richiedere nuovamente tutti i dati con cui il server ha eseguito il rendering dell'HTML, la maggior parte delle soluzioni di rendering lato server serializza la risposta dalle dipendenze dei dati di un'interfaccia utente come tag script nel documento. Poiché questo duplica una grande quantità di HTML, la reidratazione può causare più problemi della semplice interattività ritardata.

Documento HTML contenente l'interfaccia utente serializzata, i dati inlined e uno script bundle.js.

Il server restituisce una descrizione dell'interfaccia utente dell'applicazione in risposta a una richiesta di navigazione, ma restituisce anche i dati di origine utilizzati per comporre tale interfaccia utente e una copia completa dell'implementazione dell'interfaccia utente che viene quindi avviata sul client. L'interfaccia utente diventa interattiva solo dopo che bundle.js ha terminato il caricamento e l'esecuzione.

Le metriche di rendimento raccolte da siti web reali che utilizzano il rendering lato server e la reidratazione indicano che raramente è l'opzione migliore. Il motivo più importante è il suo effetto sull'esperienza utente, quando una pagina sembra pronta ma nessuna delle sue funzionalità interattive funziona.

Gli effetti negativi del rendering lato client sul TTI.

C'è speranza per il rendering lato server con reidratazione. A breve termine, l'utilizzo del rendering lato server solo per i contenuti altamente memorizzabili nella cache può ridurre il TTFB, producendo risultati simili al prerendering. La reidratazione incrementale, progressiva o parziale potrebbe essere la chiave per rendere questa tecnica più praticabile in futuro.

Eseguire lo streaming del rendering lato server e la reidratazione in modo progressivo

Negli ultimi anni il rendering lato server ha subito una serie di sviluppi.

Il rendering lato server in streaming consente di inviare HTML in blocchi che il browser può sottoporre a rendering progressivo man mano che vengono ricevuti. In questo modo puoi inviare il markup agli utenti più velocemente, accelerando l'FCP. In React, gli stream asincroni in renderToPipeableStream(), rispetto a sincrono renderToString(), significano che la contropressione viene gestita bene.

Vale la pena prendere in considerazione anche la reidratazione progressiva (React l'ha implementata). Con questo approccio, i singoli elementi di un'applicazione con rendering lato server vengono "avviati" nel tempo, anziché l'approccio comune attuale di inizializzare l'intera applicazione contemporaneamente. Questo può contribuire a ridurre la quantità di JavaScript necessaria per rendere interattive le pagine, perché consente di posticipare l'upgrade lato client delle parti a bassa priorità della pagina per evitare che blocchi il thread principale, consentendo alle interazioni dell'utente di avvenire prima che l'utente le avvii.

La reidratazione progressiva può anche aiutarti a evitare una delle insidie più comuni della reidratazione del rendering lato server: un albero DOM con rendering lato server viene eliminato e poi ricostruito immediatamente, il più delle volte perché il rendering lato client sincrono iniziale richiedeva dati non ancora pronti, spesso un Promise che non è ancora stato risolto.

Reidratazione parziale

La reidratazione parziale si è dimostrata difficile da implementare. Questo approccio è un estensione della reidratazione progressiva che analizza i singoli elementi della pagina (componenti, visualizzazioni o alberi) e identifica gli elementi con poca interattività o nessuna reattività. Per ognuna di queste parti per lo più statiche, il codice JavaScript corrispondente viene trasformato in riferimenti inerti e funzionalità decorative, riducendo la loro impronta lato client a quasi zero.

L'approccio di reidratazione parziale presenta problemi e compromessi. Presenta alcune sfide interessanti per la memorizzazione nella cache e la navigazione lato client significa che non possiamo presupporre che l'HTML con rendering lato server per le parti inerti dell'applicazione sia disponibile senza un caricamento completo della pagina.

Rendering trisomorfico

Se i service worker sono un'opzione per te, prendi in considerazione il rendering trisomorfico. Questa tecnica consente di utilizzare il rendering lato server in streaming per le navigazioni iniziali o non JavaScript e poi di fare in modo che il service worker esegua il rendering dell'HTML per le navigazioni dopo l'installazione. In questo modo puoi mantenere aggiornati i componenti e i modelli memorizzati nella cache e attivare le navigazioni in stile SPA per il rendering di nuove visualizzazioni nella stessa sessione. Questo approccio funziona meglio quando puoi condividere lo stesso codice di creazione di modelli e di routing tra il server, la pagina client e il service worker.

Rendering trisomorfico, che mostra un browser e un service worker che comunicano con il server.

Considerazioni SEO

Quando scelgono una strategia di rendering web, i team spesso prendono in considerazione l'impatto della SEO. Il rendering lato server è una scelta popolare per offrire un'esperienza "completa" che i crawler possono interpretare. I crawler possono comprendere JavaScript, ma spesso esistono limitazioni al modo in cui eseguono il rendering. Il rendering lato client può funzionare, ma spesso richiede test e overhead aggiuntivi. Più di recente, il rendering dinamico è diventato un'opzione da prendere in considerazione se la tua architettura dipende fortemente da JavaScript lato client.

Conclusione

Quando decidi un approccio al rendering, misura e comprendi quali sono i tuoi colli di bottiglia. Valuta se il rendering statico o il rendering lato server possono farti fare la maggior parte del lavoro. Va bene inviare principalmente HTML con JavaScript minimo per rendere interattiva un'esperienza. Ecco un'infografica utile che mostra lo spettro server-client:

Opzioni di rendering e relativi compromessi.

Crediti

Grazie a tutti per le recensioni e l'ispirazione:

Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson e Sebastian Markbåge.