Rendering sul web

Pubblicazione: 6 febbraio 2019, Ultimo aggiornamento: 5 gennaio 2026

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

La nostra comprensione di questo spazio si basa sul nostro lavoro in Chrome, che negli ultimi anni ha coinvolto siti di grandi dimensioni. In generale, invitiamo 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 questo modo, potrai valutare meglio i compromessi di ogni approccio di rendering dal punto di vista delle prestazioni della pagina.

Terminologia

Innanzitutto, definiamo alcuni termini che utilizzeremo.

Rendering

Rendering lato server (SSR)
Rendering di un'app sul server per inviare HTML, anziché JavaScript, al client.
Rendering lato client (CSR)
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. Tieni presente che il "prerendering" in questo senso è diverso dal prerendering del browser delle navigazioni future.
Idratazione
Esecuzione di script lato client per aggiungere lo stato dell'applicazione e l'interattività all'HTML sottoposto a rendering lato server. L'idratazione presuppone che il DOM non cambi.
Reidratazione
Sebbene spesso utilizzato per indicare la stessa cosa dell'idratazione, la reidratazione implica l'aggiornamento regolare del DOM con lo stato più recente, anche dopo l'idratazione iniziale.

Prestazioni

Time to First Byte (TTFB)
Il tempo che intercorre tra il clic su un link e il caricamento del primo byte di contenuti 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 in modo rapido agli input degli utenti.
Total Blocking Time (TBT)
Una metrica proxy per 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 di una pagina sul server in risposta alla navigazione. In questo modo si evitano ulteriori andate e ritorno per il recupero dei dati e la creazione di modelli sul client, perché il renderer li gestisce prima che il browser riceva una risposta.

In genere, il rendering lato server produce un FCP rapido. L'esecuzione della logica della pagina e del rendering sul server consente di evitare di inviare molti JavaScript al client. In questo modo, il TBT di una pagina viene ridotto, il che può anche portare a un INP più basso, perché il thread principale non viene bloccato così spesso durante il caricamento pagina. Quando il thread principale è bloccato meno spesso, le interazioni utente hanno più opportunità di essere eseguite prima.

Questo è logico, perché con il rendering lato server, in realtà stai solo inviando testo e link al browser dell'utente. Questo approccio può funzionare bene per una varietà di condizioni di dispositivi e reti 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 limitazioni della 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 proprietari può darti più budget per il resto. Tuttavia, questo approccio presenta un potenziale svantaggio: la generazione di pagine sul server richiede tempo, il che può aumentare il TTFB della pagina.

La scelta di utilizzare o meno il rendering lato server per la tua applicazione dipende in gran parte dal tipo di esperienza che 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 sue pagine di destinazione relativamente statiche, mentre esegue il prefetching del codice JavaScript per le pagine con un'interazione elevata, offrendo a queste pagine più pesanti con rendering lato client una maggiore probabilità di caricamento rapido.

Con molti framework, librerie e architetture moderni, puoi eseguire il rendering della stessa applicazione sia sul client che 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 costituiscono 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.

Tuttavia, le soluzioni più diffuse utilizzano una qualche forma di idratazione, quindi tieni presente gli approcci utilizzati dal tuo strumento.

Rendering statico

Il rendering statico avviene in fase di creazione. Questo approccio offre un FCP rapido, nonché un TBT e un 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 prevede la generazione anticipata di 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 l'edge caching.

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 varie forme e dimensioni. Strumenti come Gatsby sono progettati per dare agli sviluppatori l'impressione che il rendering della loro applicazione venga eseguito in modo dinamico, non generato come passaggio di build. Gli strumenti di generazione di siti statici come 11ty, Jekyll e Metalsmith abbracciano la loro natura statica, fornendo un approccio più basato su modelli.

Uno degli svantaggi del rendering statico è che deve generare singoli file HTML per ogni possibile URL. Questo 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 sottoposte a rendering statico sono interattive senza dover eseguire molto 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 hai la certezza che una determinata soluzione sia 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 prerenderizzate potrebbero comunque avere 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 larghezza di banda della rete in Chrome DevTools e vedere quanto JavaScript viene scaricato prima che una pagina diventi interattiva. In genere, il prerendering richiede più JavaScript per diventare interattivo e questo JavaScript tende a essere più complesso rispetto all'approccio di potenziamento progressivo utilizzato nel rendering statico.

Rendering lato server e 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 in anticipo, ritardano il TTFB o raddoppiano i dati inviati (ad esempio, gli stati in linea utilizzati da JavaScript sul client). In React, renderToString() può essere lento perché è sincrono e single-thread. Le API DOM del server React più recenti supportano lo streaming, che può inviare la parte iniziale di una risposta HTML al browser più rapidamente, mentre il resto viene ancora generato sul server.

Per ottenere un rendering lato server "corretto", può 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 altri aspetti. Spesso elabori o ricrei la stessa app due volte, una volta sul client e una sul server. Il rendering lato server che mostra i contenuti prima non riduce necessariamente il lavoro da svolgere. Se hai molto lavoro da fare sul client dopo l'arrivo di una risposta HTML generata dal server, questo può comunque comportare 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 rispetto alla semplice pubblicazione di contenuti statici di cui è stato eseguito il rendering. Se puoi fare un ulteriore sforzo, il rendering lato server più la memorizzazione nella cache HTML possono ridurre notevolmente il tempo di rendering del server. Il vantaggio del rendering lato server è la possibilità di estrarre 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 durante la creazione di una PWA. È meglio utilizzare la memorizzazione nella cache del service worker per l'intera pagina o eseguire il rendering lato server di singoli contenuti?

Rendering lato client

Il rendering lato client consiste nel 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 vengono trasmessi più dati dal server al dispositivo dell'utente, il che comporta una serie di compromessi.

Il rendering lato client può essere difficile da realizzare e mantenere veloce per i dispositivi mobili. Con un po' di lavoro per mantenere un budget JavaScript ridotto e fornire valore nel minor numero possibile di andate e ritorni, puoi fare in modo che il rendering lato client replichi quasi le prestazioni del rendering lato server puro. Puoi velocizzare il lavoro del parser fornendo script e dati critici utilizzando <link rel=preload>. Ti consigliamo inoltre di valutare l'utilizzo di pattern come PRPL per garantire che le navigazioni iniziali e successive siano istantanee.

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

Il principale svantaggio del rendering lato client è che la quantità di JavaScript richiesta tende a crescere man mano che l'applicazione cresce, 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 visualizzati.

Le esperienze che utilizzano il rendering lato client e si basano su bundle JavaScript di grandi dimensioni dovrebbero 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 è necessario. 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 della shell dell'applicazione. In combinazione con i service worker, questo può migliorare notevolmente le prestazioni percepite nelle visite ripetute, perché la pagina può caricare l'HTML della shell dell'applicazione e le dipendenze 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 pagina o i ricaricamenti di pagine intere, vengono gestite da un server che esegue il rendering dell'applicazione in HTML. Poi, il codice JavaScript e i dati utilizzati per il rendering vengono incorporati nel documento risultante. Se eseguita con attenzione, questa operazione consente di ottenere un FCP rapido come il rendering lato server, per poi "riprendere" con un nuovo rendering sul client.

Si tratta di una soluzione efficace, ma può comportare notevoli svantaggi in termini di prestazioni.

Il principale svantaggio del rendering lato server con reidratazione è che può avere un impatto negativo significativo su TBT e INP, anche se migliora FCP. Le pagine sottoposte a rendering lato server possono sembrare caricate e interattive, ma non possono rispondere effettivamente 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, confondendo e frustrando l'utente.

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

Affinché il codice JavaScript lato client possa riprendere con precisione da dove il server ha interrotto, senza richiedere nuovamente tutti i dati con cui il server ha eseguito il rendering del codice HTML, la maggior parte delle soluzioni di rendering lato server serializza la risposta dalle dipendenze dei dati di un'interfaccia utente come tag di script nel documento. Poiché questo processo duplica molto codice HTML, la reidratazione può causare problemi che vanno oltre il ritardo nell'interattività.

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

Il server restituisce una descrizione della UI dell'applicazione in risposta a una richiesta di navigazione, ma restituisce anche i dati di origine utilizzati per comporre la UI e una copia completa dell'implementazione della UI, che viene poi avviata sul client. La UI non diventa interattiva finché bundle.js non ha terminato il caricamento e l'esecuzione.

Le metriche sul rendimento raccolte da siti web reali che utilizzano il rendering lato server e la reidratazione indicano che raramente si tratta dell'opzione migliore. Il motivo più importante è l'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 rehydration. A breve termine, l'utilizzo del rendering lato server solo per i contenuti 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.

Esegui lo streaming del rendering lato server e reidrata progressivamente

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

Il rendering lato server in streaming ti consente di inviare l'HTML in blocchi che il browser può visualizzare progressivamente man mano che vengono ricevuti. In questo modo, il markup può essere inviato più rapidamente agli utenti, velocizzando l'FCP. In React, gli stream sono asincroni in renderToPipeableStream(), rispetto a quelli sincroni in renderToString(), il che significa che la contropressione viene gestita bene.

Vale la pena considerare anche la reidratazione progressiva (React l'ha implementata). Con questo approccio, i singoli componenti di un'applicazione sottoposta a rendering lato server vengono "avviati" nel tempo, anziché l'approccio comune attuale di inizializzare l'intera applicazione contemporaneamente. In questo modo, è possibile ridurre la quantità di JavaScript necessaria per rendere interattive le pagine, perché consente di posticipare l'upgrade lato client delle parti della pagina con priorità bassa per evitare che blocchi il thread principale, consentendo alle interazioni degli utenti di avvenire prima dopo che l'utente le ha avviate.

La reidratazione progressiva può anche aiutarti a evitare uno dei problemi più comuni della reidratazione del rendering lato server: un albero DOM sottoposto a 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 una Promise non ancora risolta.

Reidratazione parziale

La reidratazione parziale si è rivelata difficile da implementare. Questo approccio è un'estensione dell'idratazione progressiva che analizza singole parti della pagina (componenti, visualizzazioni o alberi) e identifica quelle con poca interattività o nessuna reattività. Per ognuna di queste parti per lo più statiche, il codice JavaScript corrispondente viene poi trasformato in riferimenti inerti e funzionalità decorative, riducendo quasi a zero l'impatto lato client.

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

Rendering trisomorfico

Se i service worker sono un'opzione per te, prendi in considerazione il rendering trisomorfico. Questa tecnica ti consente di utilizzare il rendering lato server in streaming per le navigazioni iniziali o non JavaScript e di fare in modo che il service worker si occupi del rendering dell'HTML per le navigazioni dopo l'installazione. In questo modo, i componenti e i modelli memorizzati nella cache possono essere aggiornati e le navigazioni in stile SPA possono essere attivate per il rendering di nuove visualizzazioni nella stessa sessione. Questo approccio funziona meglio quando puoi condividere lo stesso codice di templating e 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 sulla SEO

Quando scelgono una strategia di rendering web, i team spesso considerano l'impatto della SEO. Il rendering lato server è una scelta molto diffusa 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, anche 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 aiutarti a raggiungere la maggior parte dei tuoi obiettivi. Va bene inviare principalmente HTML con un minimo di JavaScript per rendere un'esperienza interattiva. Ecco un'infografica utile che mostra lo spettro server-client:

Opzioni di rendering e relativi vantaggi e svantaggi.

Crediti

Grazie a tutti per le recensioni e l'ispirazione:

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