Rendering sul Web

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

La nostra comprensione di questo ambito si basa sul lavoro svolto in Chrome in relazione a siti di grandi dimensioni nel corso degli ultimi anni. In generale, invitiamo gli sviluppatori a prendere in considerazione il rendering lato server o il rendering statico rispetto a un approccio di reidratazione completo.

Per comprendere meglio le architetture da cui prendiamo questa decisione, abbiamo bisogno di una solida comprensione di ogni approccio e di una terminologia coerente da utilizzare quando ne parliamo. Le differenze tra gli approcci di rendering aiutano a illustrare i compromessi del rendering sul web dal punto di vista delle prestazioni della pagina.

Terminologia

Rendering

Rendering lato server (SSR)
Esegui il rendering di un'app universale o lato client in HTML sul server.
Rendering lato client (CSR)
Eseguire il rendering di un'app in un browser, utilizzando JavaScript per modificare il DOM.
Reidratazione
Le visualizzazioni JavaScript "Avvio" sul client riutilizzano l'albero e i dati DOM dell'HTML sottoposto a rendering dal server.
Prerendering
Esecuzione di un'applicazione lato client al momento della creazione in modo da acquisire lo stato iniziale come codice HTML statico.

Esibizione

Tempo per il primo byte (TTFB)
Il tempo che intercorre tra il clic su un link e il caricamento del primo byte dei contenuti nella nuova pagina.
First Contentful Paint (FCP)
Il momento in cui i contenuti richiesti (corpo dell'articolo ecc.) diventano visibili.
Interazione con Next Paint (INP)
Una metrica rappresentativa che valuta se una pagina risponde costantemente rapidamente agli input degli utenti.
Tempo di blocco totale (TBT)
Una metrica proxy per INP che calcola per quanto tempo il thread principale è stato bloccato durante il caricamento pagina.

Rendering lato server

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

Il rendering lato server generalmente produce un valore FCP veloce. L'esecuzione della logica della pagina e il rendering sul server ti consentono di evitare di inviare al client molto codice JavaScript. Ciò contribuisce a ridurre il TBT di una pagina, il che può anche portare a un INP inferiore, in quanto il thread principale non viene bloccato spesso durante il caricamento della pagina. Quando il thread principale viene bloccato con minore frequenza, le interazioni degli utenti hanno maggiori opportunità di essere eseguite prima. Questo ha senso, perché con il rendering lato server, invii davvero solo testo e link al browser dell'utente. Questo approccio può funzionare bene per diverse condizioni di dispositivi e reti e offre interessanti ottimizzazioni del browser, come l'analisi dei documenti in streaming.

Diagramma che mostra il rendering lato server e l'esecuzione di JS che interessano FCP e TTI.
FCP e TTI con rendering lato server.

Con il rendering lato server, è meno probabile che gli utenti rimangano in attesa dell'esecuzione di JavaScript associato alla CPU prima di poter utilizzare il tuo sito. Anche quando non puoi evitare di utilizzare JS di terze parti, utilizzare il rendering lato server per ridurre i costi JavaScript proprietari può comportare un aumento del budget per il resto. Tuttavia, esiste un potenziale compromesso con questo approccio: la generazione di pagine sul server richiede tempo e questo può aumentare il TTFB della pagina.

La quantità di rendering lato server sufficiente per la tua applicazione dipende in gran parte dal tipo di esperienza che stai creando. È da tempo dibattuto sulle applicazioni corrette del rendering lato server rispetto al rendering lato client, tuttavia si può 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, il server Netflix visualizza le sue pagine di destinazione relativamente statiche, precaricando al contempo il codice JS per le pagine con interazioni elevate. In questo modo, queste pagine più pesanti sotto il rendering dal client hanno maggiori probabilità di caricarsi rapidamente.

Molti framework, librerie e architetture moderne consentono di eseguire il rendering della stessa applicazione sia sul client che sul server. Puoi usare queste tecniche per il rendering lato server. Tuttavia, le architetture in cui il rendering avviene sia sul server sia sul client sono la propria classe di soluzioni con caratteristiche prestazionali e compromessi molto diversi. Gli utenti di React possono utilizzare API DOM server o soluzioni basate su queste, 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 il valore Universale. Le soluzioni più diffuse, tuttavia, utilizzano una qualche forma di idratazione, quindi presta attenzione agli approcci utilizzati dal tuo strumento.

Rendering statico

Il rendering statico si verifica al momento della creazione. Questo approccio offre un FCP veloce, oltre a un TBT e un INP più bassi, purché venga limitata la quantità di codice JS lato client sulle tue pagine. A differenza del rendering lato server, raggiunge anche un TTFB costantemente veloce, in quanto l'HTML di una pagina non deve essere generato dinamicamente sul server. In genere, il rendering statico prevede la produzione di un file HTML separato per ogni URL in anticipo rispetto al tempo. Con le risposte HTML generate in anticipo, puoi eseguire il deployment di rendering statici su più reti CDN per sfruttare la memorizzazione in una cache perimetrale.

Diagramma che mostra il rendering statico e l'esecuzione facoltativa di JS che interessano 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 sembrare agli sviluppatori che la loro applicazione venga visualizzata in modo dinamico, non come passaggio di build. Gli strumenti di generazione di siti statici come 11ty, Jekyll e Metalsmith offrono 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 URL possibile. Ciò può essere difficile o addirittura impossibile quando non puoi prevedere quali saranno questi URL in anticipo o 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 rendono agevole la creazione di pagine dai componenti. Tuttavia, il rendering statico e il prerendering funzionano in modo diverso: le pagine visualizzate in modo statico sono interattive senza dover eseguire molto codice 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 davvero interattive.

Se non hai la certezza che una determinata soluzione sia di rendering statico o di prerendering, prova a disattivare JavaScript e carica la pagina che vuoi testare. Per le pagine sottoposte a rendering statico, la maggior parte delle funzionalità interattive esistono ancora senza JavaScript. Le pagine sottoposte a prerendering potrebbero comunque avere alcune funzionalità di base, ad esempio 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 verificare il numero di JavaScript scaricato prima che una pagina diventi interattiva. In genere, il prerendering richiede più JavaScript per diventare interattivo e JavaScript tende a essere più complesso dell'approccio di miglioramento 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 significativi costi generali di calcolo. Molte soluzioni di rendering lato server non eseguono lo svuotamento anticipato, ritardano il TTFB o raddoppiano i dati inviati (ad esempio gli stati incorporati utilizzati da JavaScript sul client). In React, renderToString() può essere lento perché è sincrono e a thread singolo. Le API DOM dei server React più recenti supportano lo streaming, che può ricevere al browser la parte iniziale di una risposta HTML prima mentre il resto viene ancora generato sul server.

Ottenere un rendering lato server "corretto" può comportare la ricerca o la creazione di una soluzione per la memorizzazione dei componenti, la gestione del consumo di memoria, l'utilizzo di tecniche di memorizzazione e altri problemi. Spesso elabori o ricrea la stessa app due volte, una sul client e una sul server. Il rendering lato server che mostra i contenuti in breve tempo non comporta necessariamente un carico di lavoro minore. Se hai molto lavoro sul client dopo che una risposta HTML generata dal server arriva sul client, ciò può comunque portare a un aumento del TBT e dell'INP 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 visualizzati statici. Se puoi aggiungere ulteriori risorse, il rendering lato server e la memorizzazione nella cache HTML possono ridurre notevolmente i tempi di rendering del server. Il vantaggio del rendering lato server è la capacità di estrarre più dati "live" e rispondere a un insieme di richieste più completo di quanto non sia possibile con il rendering statico. Le pagine che richiedono una 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 a pagina intera o semplicemente eseguire il rendering del server di singoli contenuti?

Rendering lato client

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

Il rendering lato client può essere difficile da realizzare e mantenere veloce per i dispositivi mobili. Con un piccolo lavoro per mantenere un budget JavaScript adeguato e offrire valore nel minor numero di round-trip possibile, puoi ottenere il rendering lato client per replicare quasi le prestazioni del rendering lato server puro. Puoi far funzionare il parser più rapidamente fornendo script e dati critici con <link rel=preload>. Ti consigliamo inoltre di utilizzare 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 richiesto tende ad aumentare con l'aumento di un'applicazione, il che può influire sull'INP di una pagina. Ciò diventa particolarmente difficile con l'aggiunta di nuove librerie JavaScript, polyfill e codice di terze parti, che competono per la potenza di elaborazione e che 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 una suddivisione aggressiva del codice per ridurre TBT e INP durante il caricamento della pagina, nonché JavaScript con caricamento lento per pubblicare solo ciò di cui l'utente ha bisogno, quando è necessario. Per esperienze con interattività minima o nulla, il rendering lato server può rappresentare una soluzione più scalabile a questi problemi.

Per chi crea applicazioni a pagina singola, identificare le parti fondamentali dell'interfaccia utente condivise dalla maggior parte delle pagine consente di applicare la tecnica di memorizzazione della shell dello shell dell'applicazione. Combinata con i service worker, può migliorare notevolmente le prestazioni percepite in caso di visite ripetute, perché la pagina può caricare molto rapidamente il codice HTML e le dipendenze della shell dell'applicazione da CacheStorage.

La reidratazione combina il rendering lato server e lato client

La reidratazione è un approccio che cerca di appianare i compromessi tra il rendering lato client e lato server eseguendo entrambe le operazioni. Le richieste di navigazione, ad esempio i caricamenti di pagine intere, vengono gestite da un server che esegue il rendering dell'applicazione in HTML, quindi il codice JavaScript e i dati utilizzati per il rendering vengono incorporati nel documento risultante. Se eseguita con attenzione, si ottiene un FCP veloce come il rendering lato server, quindi viene "rilevato" eseguendo di nuovo il rendering sul client. Questa è una soluzione efficace, ma può presentare notevoli svantaggi in termini di prestazioni.

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

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

Affinché il codice JavaScript lato client "riprenda" correttamente dal punto in cui 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 script nel documento. Poiché questo duplica molti HTML, reidratazione può causare più problemi rispetto al solo ritardo dell'interattività.

Documento HTML contenente UI serializzata, dati incorporati e uno script bundle.js
Codice duplicato nel documento HTML.

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 l'interfaccia utente e una copia completa dell'implementazione dell'interfaccia utente che viene avviata sul client. La UI diventa interattiva solo al termine del caricamento e dell'esecuzione di bundle.js.

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

Diagramma che mostra il rendering del cliente che influisce negativamente sul TTI.
Gli effetti del rendering lato client su TTI.

Tuttavia, c'è speranza per il rendering lato server con la reidratazione. Nel breve termine, soltanto l'utilizzo del rendering lato server per contenuti altamente memorizzabili nella cache può ridurre il tempo di risposta totale, producendo risultati simili a quelli del prerendering. La reidratazione incrementale, progressiva o parziale potrebbe essere la chiave per rendere questa tecnica più utilizzabile in futuro.

Riprodurre in streaming il rendering lato server e reidratare in modo progressivo

Il rendering lato server ha avuto una serie di sviluppi negli ultimi anni.

Il rendering lato server dei flussi consente di inviare il codice HTML in blocchi che il browser può visualizzare progressivamente non appena viene ricevuto. In questo modo, puoi ottenere il markup per i tuoi utenti più rapidamente, velocizzando il tuo FCP. In React, i flussi sono asincroni in renderToPipeableStream(), rispetto al valore sincrono nel renderToString(), significa che la contropressione viene gestita correttamente.

Vale anche la pena di valutare la reidratazione progressiva, che è stata implementata da React. Con questo approccio, le singole parti di un'applicazione sottoposta a rendering dal server vengono "avviate" nel tempo, invece che rispetto all'attuale approccio comune di inizializzare l'intera applicazione contemporaneamente. Ciò può aiutarti a ridurre la quantità di JavaScript necessario per rendere interattive le pagine, perché ti consente di posticipare l'upgrade lato client di parti a bassa priorità della pagina per evitare che blocchi il thread principale, consentendo le interazioni degli utenti prima dopo l'avvio da parte dell'utente.

La reidratazione progressiva può anche aiutarti a evitare uno dei più comuni errori di reidratazione nel rendering lato server: un albero DOM sottoposto a rendering server viene eliminato e poi ricostruito immediatamente, il più delle volte perché il rendering sincrono iniziale lato client richiedeva dati non ancora pronti, spesso un Promise non ancora risolto.

Reidratazione parziale

La reidratazione parziale si è dimostrata difficile da implementare. Questo approccio è un'estensione della reidratazione progressiva che analizza singole parti della pagina (componenti, visualizzazioni o alberi) e identifica i pezzi 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 caratteristiche decorative, riducendo l'impatto lato client quasi pari a zero.

L'approccio all'idratazione parziale porta con sé problemi e compromissioni. Presenta alcune sfide interessanti per la memorizzazione nella cache e la navigazione lato client significa che non possiamo presumere che l'HTML sottoposto a rendering dal server per parti inerti dell'applicazione sia disponibile senza un caricamento completo della pagina.

Rendering trisomorfico

Se i service worker fanno per te, prendi in considerazione il rendering trisomorfico. Si tratta di una tecnica che consente di utilizzare il rendering lato server di streaming per le navigazioni iniziali o non JS e di fare in modo che il service worker si occupi del rendering dell'HTML per le navigazioni dopo l'installazione. In questo modo è possibile mantenere aggiornati i componenti e i modelli memorizzati nella cache e consentire le navigazioni in stile SPA per il rendering di nuove viste nella stessa sessione. Questo approccio funziona al meglio quando puoi condividere lo stesso modello e lo stesso codice di routing tra server, pagina client e service worker.

Diagramma del rendering trisomorfico, che mostra un browser e un service worker che comunicano con il server.
Un diagramma di come funziona il rendering trisomorfico.

Considerazioni sulla SEO

Quando scelgono una strategia di rendering per il web, i team spesso valutano l'impatto della SEO. Il rendering lato server è una scelta popolare per offrire un'esperienza di "aspetto completo" che i crawler sono in grado di interpretare. I crawler comprendono JavaScript, ma spesso il modo in cui viene eseguito il rendering presenta delle limitazioni. Il rendering lato client può funzionare, ma spesso richiede ulteriori test e costi aggiuntivi. Più di recente, anche il rendering dinamico è diventato un'opzione che vale la pena prendere in considerazione se la tua architettura dipende molto da JavaScript lato client.

In caso di dubbi, lo strumento Test di ottimizzazione mobile è un ottimo modo per verificare che l'approccio che hai scelto faccia ciò che ti aspetti. Mostra un'anteprima visiva di come ogni pagina appare al crawler di Google, dei contenuti HTML in serie che trova dopo l'esecuzione di JavaScript e di eventuali errori riscontrati durante il rendering.

Screenshot dell&#39;interfaccia utente del test di ottimizzazione mobile.
UI del test di ottimizzazione mobile.

Conclusione

Quando decidi un approccio al rendering, misura e comprendi quali sono i colli di bottiglia. Valuta se il rendering statico o lato server può farti aiutare. È consigliabile inviare asset HTML con un minimo JavaScript per avere un'esperienza interattiva. Ecco un'infografica utile che mostra lo spettro server-client:

Infografica che mostra la gamma di opzioni descritte in questo articolo.
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