Rendering sul Web

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

La nostra comprensione di questo spazio si basa 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 scegliamo quando prendiamo questa decisione, abbiamo bisogno di una terminologia coerente e di un framework condiviso per ogni approccio. In questo modo, 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)
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 al momento della creazione per acquisirne lo stato iniziale come HTML statico.
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 per indicare la stessa cosa dell'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 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 in modo rapido e costante 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 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 rapido. L'esecuzione della logica della pagina e il rendering sul server ti consentono di evitare l'invio di molto JavaScript al client. Ciò contribuisce a ridurre il TTBT 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 degli utenti hanno più opportunità di essere eseguite prima.

Ciò ha senso, 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 di rete e dispositivo e apre interessanti ottimizzazioni del browser, come l'analisi in streaming dei documenti.

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 vincolato alla 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 tuoi costi JavaScript può darti più budget per il resto. Tuttavia, questo approccio presenta un potenziale compromesso: 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 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 prefetching il 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 che 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 costituiscono una classe di soluzioni a sé stante con caratteristiche e compromessi di rendimento molto diversi. Gli utenti di React possono utilizzare le API DOM lato 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.

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

Rendering statico

Il rendering statico avviene al momento della build. 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 consiste nel produrre in anticipo un file HTML separato per ogni URL. Con le risposte HTML generate in anticipo, puoi eseguire il deployment di rendering statici su più CDN per sfruttare la memorizzazione nella cache perimetrale.

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 sì che gli sviluppatori abbiano l'impressione che 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 sfruttano 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 conoscere Gatsby, Next.js static export o Navi, tutti strumenti 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 richiedere l'esecuzione di 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 renderizzate staticamente, la maggior parte delle funzionalità interattive esiste ancora senza JavaScript. Le pagine pre-renderizzate potrebbero comunque avere alcune funzionalità di base come i link con JavaScript disabilitato, ma la maggior parte della pagina è inerte.

Un altro test utile consiste nell'utilizzare la limitazione della rete in Chrome DevTools e vedere quanto JavaScript viene scaricato 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 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 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 può inviare la parte iniziale di una risposta HTML al browser più rapidamente mentre il resto viene ancora generato sul server.

Per ottenere il rendering lato server "giusto", potrebbe essere necessario trovare o creare una soluzione per la memorizzazione nella cache dei componenti, gestire il consumo di memoria, utilizzare tecniche di memoizzazione e altri aspetti. Spesso elabori o ricompili la stessa app due volte, una volta sul client e una volta 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 l'arrivo di una risposta HTML generata dal server sul client, ciò può comunque portare a TBT e 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 sottoposti a rendering. Se puoi fare un lavoro aggiuntivo, 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 a pagina intera o il rendering lato server di singoli contenuti?

Rendering lato client

Il rendering lato client significa visualizzare le 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 inviati più dati al dispositivo dell'utente, con il proprio insieme 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 rigoroso e fornire valore nel minor numero possibile di round trip, puoi fare in modo che il rendering lato client replichi quasi le prestazioni del rendering lato server puro. Puoi far funzionare il parser più rapidamente 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 istantanee.

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. 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 spesso devono essere elaborati prima che il contenuto di una pagina possa essere visualizzato.

Le esperienze che utilizzano il rendering lato client e si basano su bundle JavaScript di grandi dimensioni devono prendere in considerazione la divisione aggressiva del codice per ridurre TBT e INP durante il caricamento della pagina, nonché il caricamento differito di JavaScript per mostrare 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 della shell dell'applicazione. In combinazione con i service worker, ciò può migliorare notevolmente le prestazioni percepite nelle visite ripetute, perché la pagina può caricare l'HTML e le dipendenze della shell dell'applicazione da CacheStorage molto rapidamente.

L'idratazione combina il rendering lato server e lato client

L'idratazione è un approccio che mitiga i compromessi tra il rendering lato client e lato server eseguendo entrambi. Le richieste di navigazione, come i caricamenti o i ricaricamenti di pagine complete, vengono gestite da un server che esegue il rendering dell'applicazione in HTML. Quindi, il JavaScript e i dati utilizzati per il rendering vengono incorporati nel documento risultante. Se eseguito con attenzione, questo metodo consente di ottenere un FCP rapido come il rendering lato server, quindi "riprende" il rendering sul client.

Si tratta di una soluzione efficace, ma può presentare 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 FCP. Le pagine sottoposte a 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 vengono collegati. Su dispositivo mobile, questa operazione può richiedere minuti, creando confusione e frustrazione nell'utente.

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

Affinché JavaScript lato client possa riprendere con precisione da dove si era interrotto il server, 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 gran parte dell'HTML, la reidratazione può causare più problemi della semplice interattività ritardata.

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. L'interfaccia utente 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 è 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.

Esiste la possibilità di eseguire il rendering lato server con reidratazione. Nel 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ù fattibile in futuro.

Esegui il rendering lato server dello stream e l'idratazione progressiva

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

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

Anche la reidratazione progressiva è un aspetto da considerare (React l'ha implementata). Con questo approccio, i singoli componenti di un'applicazione sottoposta a rendering lato server vengono "avviati" nel tempo, anziché con 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 a bassa priorità della pagina per evitare che blocchi il thread principale, consentendo le interazioni dell'utente prima che vengano 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 distrutto e poi ricostruito immediatamente, molto spesso perché il rendering sincrono iniziale lato client richiedeva dati non ancora pronti, spesso una Promise che non è ancora stata risolta.

Reidratazione parziale

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

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

Rendering trisomorfo

Se i service worker sono un'opzione per te, valuta il rendering trisomorfico. Questa tecnica ti consente di utilizzare il rendering lato server in streaming per le navigazioni iniziali o non JavaScript e poi di far sì 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 mantenuti aggiornati e le navigazioni in stile SPA possono essere utilizzate per il rendering di nuove visualizzazioni nella stessa sessione. Questo approccio funziona meglio quando puoi condividere lo stesso codice di modelli 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 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 ci sono limitazioni al modo in cui vengono visualizzati. 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 molto da JavaScript lato client.

In caso di dubbi, lo strumento 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.

L'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 lato server può aiutarti a raggiungere il tuo obiettivo. Va bene spedire principalmente HTML con JavaScript minimo per rendere interattiva un'esperienza. Ecco un'infografica utile che mostra lo spettro client-server:

Opzioni di rendering e relativi compromessi.

Crediti {:#credits}

Grazie a tutti per le recensioni e l'ispirazione:

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