Rendering sul Web

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

La nostra comprensione di questo spazio si basa sul nostro lavoro in Chrome con siti di grandi dimensioni negli ultimi anni. In generale, incoraggiamo gli sviluppatori a prendere in considerazione il rendering lato server o il rendering statico anziché un approccio di reidratazione completo.

Per comprendere meglio le architetture tra cui scegliere quando prendiamo questa decisione, dobbiamo avere una conoscenza solida di ogni approccio e 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 del rendimento della pagina.

Innanzitutto, definiamo la terminologia che utilizzeremo.

Rendering

Rendering lato server (SSR)
Eseguire il 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.
Reidratazione
"Avvio" delle visualizzazioni JavaScript sul client in modo che riutilizzino la struttura DOM e i dati dell'HTML visualizzato dal server.
Prerendering
Eseguire un'applicazione lato client in fase di compilazione per acquisire il relativo stato iniziale come HTML statico.

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 (testo dell'articolo e così via) diventano visibili.
Interaction to Next Paint (INP)
Una metrica rappresentativa che valuta se una pagina risponde in modo coerente e rapido agli input degli utenti.
Total Blocking Time (TBT)
Una metrica proxy per l'INP che calcola il tempo di blocco del thread principale durante il caricamento della pagina.

Rendering lato server

Il rendering lato server genera il codice HTML completo di una pagina sul server in risposta alla navigazione. In questo modo vengono evitati ulteriori viaggi di andata 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 il rendering sul server ti consentono di evitare di inviare molto codice JavaScript al client. In questo modo, si riduce il TBT di una pagina, il 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 utente hanno più opportunità di essere eseguite prima. Questo è logico, perché con il rendering lato server invii solo testo e link al browser dell'utente. Questo approccio può funzionare bene per una serie di condizioni del dispositivo e della rete e offre ottimizzazioni interessanti del browser come l'analisi dei documenti in streaming.

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

Con il rendering lato server, è meno probabile che gli utenti debbano attendere l'esecuzione di JavaScript legato alla CPU prima di poter utilizzare il tuo sito. Anche se non puoi evitare il codice 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 delle pagine sul server richiede tempo, il che può aumentare il TTFB della pagina.

Il fatto che il rendering lato server sia sufficiente per la tua applicazione dipende in gran parte dal tipo di esperienza che stai creando. Esiste un dibattito da tempo 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 ibride. Ad esempio, Netflix esegue il rendering lato server delle sue pagine di destinazione relativamente statiche, mentre prefetching del codice JS per le pagine con molte interazioni, offrendo a queste pagine con un rendering lato client più pesante maggiori probabilità di caricarsi rapidamente.

Molti framework, librerie e architetture moderni ti consentono di 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 che sul client sono una classe di soluzioni a sé stante con caratteristiche e compromessi di prestazioni molto diversi. Gli utenti di React possono utilizzare API DOM del 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 Universal. Tuttavia, la maggior parte delle soluzioni più utilizzate utilizza una qualche forma di idratazione, quindi tieni presente gli approcci utilizzati dal tuo strumento.

Rendering statico

Il rendering statico avviene al momento della compilazione. Questo approccio offre un'esperienza utente rapida e anche un tempo di caricamento della pagina e un tempo di risposta più ridotti, a condizione che tu limiti la quantità di codice JavaScript lato client nelle tue pagine. A differenza del rendering lato server, consente inoltre di ottenere un TTFB costantemente veloce, in quanto il codice HTML di una pagina non deve essere generato dinamicamente sul server. In genere, il rendering statico consiste nella produzione di un file HTML separato per ogni URL in anticipo. Con le risposte HTML generate in anticipo, puoi eseguire il deployment di rendering statici su più CDN per sfruttare la memorizzazione nella cache di EDGE.

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

Soluzioni per il rendering statico disponibili in tutte le forme e dimensioni. Strumenti come Gatsby sono progettati per far sentire gli sviluppatori come se la loro applicazione venga visualizzata in modo dinamico, non generata come passaggio di compilazione. Gli strumenti di generazione di siti statici come 11ty, Jekyll e Metalsmith adottano la loro natura statica, offrendo un approccio più basato su modelli.

Uno dei lati negativi del rendering statico è che deve generare singoli file HTML per ogni URL possibile. Questo può essere complicato o addirittura non fattibile se 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 conoscere Gatsby, l'esportazione statica di Next.js o Navi, che consentono tutti di creare comodamente pagine da componenti. Tuttavia, il rendering statico e il prerendering si comportano diversamente: le pagine con rendering statico sono interattive senza dover eseguire molto codice JavaScript lato client, mentre il prerendering migliora il FCP di un'applicazione a pagina singola che deve essere avviata sul client per rendere le pagine veramente interattive.

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

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

Confronto tra il rendering lato server e il rendering statico

Il rendering lato server non è la soluzione migliore per tutto, perché la sua natura dinamica può comportare costi di overhead computazionale significativi. Molte soluzioni di rendering lato server non eseguono lo svuotamento anticipato, 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 a thread singolo. Le API DOM del server React più recenti supportano lo streaming, che consente di inviare la parte iniziale di una risposta HTML al browser prima, mentre il resto viene ancora generato sul server.

Per eseguire correttamente 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 altri problemi. Spesso elabori o ricostruisci la stessa app due volte, una volta sul client e una volta sul server. Il rendering lato server che mostra i contenuti più rapidamente non comporta necessariamente meno lavoro. Se hai molto lavoro sul client dopo l'arrivo di una risposta HTML generata dal server, questo può comunque portare a un aumento di TBT e INP per il tuo sito web.

Il rendering lato server produce HTML on demand per ogni URL, ma può essere più lento rispetto alla pubblicazione di contenuti con rendering statico. Se puoi fare un piccolo sforzo in più, il rendering lato server e la cache HTML possono ridurre notevolmente il tempo di rendering del server. Il vantaggio del rendering lato server è la possibilità di estrarre più dati "in tempo reale" e di rispondere a un insieme più completo di richieste rispetto a quanto sia 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.

Anche il rendering lato server può presentare decisioni interessanti durante la creazione di una PWA: è meglio utilizzare il caching del service worker di una pagina intera o semplicemente il rendering lato server di singoli elementi di contenuto?

Rendering lato client

Per rendering lato client si intende 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 più dati vengono trasmessi al dispositivo dell'utente dal server, con un proprio insieme di compromessi.

Il rendering lato client può essere difficile da realizzare e mantenere veloce per i dispositivi mobili. Con un po' di impegno per mantenere un budget JavaScript limitato e offrire valore nel minor numero possibile di round-trip, puoi ottenere un rendering lato client che quasi raggiunga il rendimento del rendering lato server puro. Puoi far funzionare il parser più velocemente caricando script e dati critici utilizzando <link rel=preload> Ti consigliamo inoltre di utilizzare pattern come PRPL per assicurarti 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 il rendering lato client.

Il principale svantaggio del rendering lato client è che la quantità di codice JavaScript necessaria tende ad aumentare con la crescita dell'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 devono spesso 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 del codice aggressiva per ridurre il TBT e l'INP durante il caricamento della pagina, nonché il caricamento lento di JavaScript per caricare 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, identificare le parti di base dell'interfaccia utente condivise dalla maggior parte delle pagine consente di applicare la tecnica di caching della shell dell'applicazione. Se combinato con i service worker, questo può migliorare notevolmente il rendimento percepito durante le visite ripetute, perché la pagina può caricare molto rapidamente il codice HTML della shell dell'applicazione e le dipendenze da CacheStorage.

La reidratazione combina il rendering lato server e lato client

La reidratazione è un approccio che tenta di attenuare i compromessi tra il rendering lato client e il rendering lato server utilizzando entrambi. Le richieste di navigazione, come i caricamenti o i ricaricamenti completi della pagina, 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, questa tecnica consente di ottenere un FCP rapido come il rendering lato server, quindi "riprende" eseguendo nuovamente il rendering sul client. Si tratta di una soluzione efficace, ma può avere notevoli svantaggi in termini di prestazioni.

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

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

Affinché il codice JavaScript lato client possa "riprendere" esattamente da dove si è interrotto il server, senza richiedere di nuovo tutti i dati con cui il server ha eseguito il rendering dell'HTML, la maggior parte delle soluzioni di rendering lato server esegue la serializzazione della risposta dalle dipendenze dei dati dell'interfaccia utente come tag script nel documento. Poiché questo comporta la duplicazione di molto codice HTML, la reidratazione può causare più problemi oltre a un'interattività ritardata.

Documento HTML
    contenente interfaccia utente serializzata, dati in linea 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. L'interfaccia utente diventa interattiva solo al termine del caricamento e dell'esecuzione di bundle.js.

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 è il suo effetto sull'esperienza utente, quando una pagina sembra pronta, ma nessuna delle sue funzionalità interattive funziona.

Diagramma
    che mostra il rendering del client che influisce negativamente sul TTI.
Effetti del rendering lato client sul TTI.

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

Esegui il rendering lato server in streaming e reidrata gradualmente

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

Il rendering lato server in streaming consente di inviare HTML in blocchi che il browser può eseguire il rendering progressivamente man mano che li riceve. In questo modo, il markup può essere inviato più rapidamente agli utenti, velocizzando il FCP. In React, gli stream asincroni in renderToPipeableStream(), rispetto a quelli sincroni in renderToString(), indicano che la backpressure viene gestita bene.

Vale la pena prendere in considerazione anche la reidratazione progressiva, che React ha implementato. Con questo approccio, i singoli componenti di un'applicazione visualizzata sul server vengono "avviati" nel tempo, anziché con l'attuale approccio comune di inizializzazione dell'intera applicazione contemporaneamente. In questo modo puoi ridurre la quantità di codice JavaScript necessaria per rendere interattive le pagine, perché ti consente di posticipare l'upgrade lato client delle parti della pagina con priorità bassa per evitare che blocchi il thread principale, consentendo così alle interazioni utente di avvenire più rapidamente dopo che l'utente le avvia.

La reidratazione progressiva può anche aiutarti a evitare uno dei problemi più comuni della reidratazione del rendering lato server: una struttura DOM visualizzata lato server viene distrutta e poi ricostruita immediatamente, il più delle volte perché il rendering lato client sincrono iniziale richiedeva dati non ancora pronti, spesso un Promise che non è stato ancora risolto.

Reidratazione parziale

La reidratazione parziale si è dimostrata difficile da implementare. Questo approccio è un'estensione della reidratazione progressiva che analizza i singoli componenti della pagina (componenti, visualizzazioni o alberi) e identifica quelli 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, riducendone l'impronta lato client a quasi zero.

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

Rendering trisomorfo

Se i service worker sono un'opzione per te, valuta la possibilità di utilizzare il rendering trisomorfo. Si tratta di una tecnica che consente di utilizzare il rendering lato server in streaming per le navigazioni iniziali o non JS e poi di fare in modo che il tuo service worker si occupi del 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 templating e routing tra il server, la pagina del client e il service worker.

Rendimento trisomorfo che mostra un browser e un worker di servizio che comunicano con il server.
Un diagramma di come funziona il rendering trisomorfo.

Considerazioni sulla SEO

Quando scelgono una strategia di rendering web, i team spesso prendono in considerazione l'impatto della SEO. Il rendering lato server è una scelta molto utilizzata per offrire un'esperienza "completa" che i crawler possono interpretare. I crawler possono comprendere JavaScript, ma spesso esistono limitazioni al loro 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 molto da JavaScript lato client.

In caso di dubbi, lo strumento di test di ottimizzazione mobile è un ottimo modo per verificare che l'approccio scelto funzioni come previsto. Mostra un'anteprima visiva dell'aspetto di qualsiasi pagina per il crawler di Google, i contenuti HTML serializzati che trova dopo l'esecuzione di JavaScript e gli eventuali errori riscontrati durante il rendering.

Interfaccia utente del Test di ottimizzazione mobile.
Interfaccia utente del Test di ottimizzazione mobile
.

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 può aiutarti a risolvere il problema. È sufficiente pubblicare principalmente HTML con un minimo di JavaScript per ottenere un'esperienza interattiva. Ecco una pratica infografica che mostra lo spettro del server-client:

Infografica che mostra l&#39;ampio spettro di opzioni descritte in questo articolo.
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