Analisi delle prestazioni del percorso di rendering critico

Ilya Grigorik
Ilya Grigorik

Data di pubblicazione: 31 marzo 2014

L'identificazione e la risoluzione dei colli di bottiglia delle prestazioni del percorso di rendering critico richiede una buona conoscenza delle insidie comuni. Un tour guidato per identificare i modelli di rendimento comuni ti aiuterà a ottimizzare le tue pagine.

L'ottimizzazione del percorso di rendering critico consente al browser di visualizzare la pagina il più rapidamente possibile: le pagine più veloci si traducono in un maggiore coinvolgimento, in un numero maggiore di pagine visualizzate e in un miglioramento delle conversioni. Per ridurre al minimo il tempo che un visitatore trascorre a visualizzare una schermata vuota, dobbiamo ottimizzare le risorse da caricare e l'ordine in cui vengono caricate.

Per illustrare questa procedura, inizia con il caso più semplice possibile e crea gradualmente la pagina in modo da includere risorse, stili e logica di applicazione aggiuntivi. Durante la procedura, ottimizzeremo ogni caso e vedremo anche dove possono verificarsi errori.

Finora ci siamo concentrati esclusivamente su ciò che accade nel browser dopo che la risorsa (file CSS, JS o HTML) è disponibile per l'elaborazione. Abbiamo ignorato il tempo necessario per recuperare la risorsa dalla cache o dalla rete. Supponiamo che:

  • Un round trip di rete (latenza di propagazione) al server costa 100 ms.
  • Il tempo di risposta del server è di 100 ms per il documento HTML e di 10 ms per tutti gli altri file.

L'esperienza Hello World

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

Prova

Inizia con un markup HTML di base e una singola immagine, senza CSS o JavaScript. Quindi, apri il riquadro Rete in Chrome DevTools ed esamina la struttura a cascata delle risorse risultante:

CRP

Come previsto, il download del file HTML ha richiesto circa 200 ms. Tieni presente che la parte trasparente della linea blu rappresenta il periodo di tempo durante il quale il browser attende sulla rete senza ricevere byte di risposta, mentre la parte solida indica il tempo necessario per completare il download dopo aver ricevuto i primi byte di risposta. Il download del codice HTML è molto piccolo (<4K), quindi è sufficiente un solo viaggio di andata e ritorno per recuperare il file completo. Di conseguenza, il recupero del documento HTML richiede circa 200 ms, metà del tempo trascorso in attesa sulla rete e l'altra metà in attesa della risposta del server.

Quando i contenuti HTML diventano disponibili, il browser analizza i byte, li converte in token e crea la struttura DOM. Tieni presente che DevTools riporta comodamente il tempo dell'evento DOMContentLoaded in basso (216 ms), che corrisponde anche alla linea verticale blu. L'intervallo tra la fine del download dell'HTML e la linea verticale blu (DOMContentLoaded) è il tempo necessario al browser per creare la struttura DOM, in questo caso solo pochi millisecondi.

Tieni presente che la nostra "foto fantastica" non ha bloccato l'evento domContentLoaded. Risulta poi che possiamo costruire l'albero di rendering e persino colorare la pagina senza attendere tutti gli asset presenti sulla pagina: non tutte le risorse sono fondamentali per la pubblicazione della prima visualizzazione rapida. Infatti, quando parliamo del percorso di rendering critico, di solito ci riferiamo al markup HTML, a CSS e a JavaScript. Le immagini non bloccano il rendering iniziale della pagina, anche se dovremmo anche provare a visualizzarle il prima possibile.

Detto questo, l'evento load (noto anche come onload) è bloccato nell'immagine: DevTools registra l'evento onload a 335 ms. Ricorda che l'evento onload indica il punto in cui tutte le risorse richieste dalla pagina sono state scaricate ed elaborate. A questo punto, la rotellina di caricamento può smettere di girare nel browser (la linea verticale rossa nella struttura a cascata).

Aggiunta di JavaScript e CSS

La nostra pagina "Esperienza "Hello World" sembra di base, ma sotto il cofano c'è molto di più. In pratica, avremo bisogno di più del codice HTML: è probabile che avremo un foglio di stile CSS e uno o più script per aggiungere interattività alla nostra pagina. Aggiungili entrambi per vedere cosa succede:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Script</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="timing.js"></script>
  </body>
</html>

Prova

Prima di aggiungere JavaScript e CSS:

CRP del DOM

Con JavaScript e CSS:

DOM, CSSOM, JS

L'aggiunta di file CSS e JavaScript esterni aggiunge due richieste aggiuntive alla nostra struttura a cascata, tutte inviate dal browser all'incirca nello stesso momento. Tuttavia, tieni presente che ora esiste una differenza di tempo molto minore tra gli eventi domContentLoaded e onload.

Che cosa è successo?

  • A differenza dell'esempio in formato HTML, dobbiamo anche recuperare e analizzare il file CSS per creare il CSSOM e abbiamo bisogno sia del DOM che del CSSOM per creare l'albero di rendering.
  • Poiché la pagina contiene anche un file JavaScript che blocca il parser, l'evento domContentLoaded viene bloccato fino a quando il file CSS non viene scaricato e analizzato: poiché JavaScript potrebbe eseguire query sul CSSOM, dobbiamo bloccare il file CSS fino a quando non viene scaricato prima di poter eseguire JavaScript.

Cosa succede se sostituisci il nostro script esterno con uno script incorporato? Anche se lo script è incorporato direttamente nella pagina, il browser non può eseguirlo fino a quando non viene creato il CSSOM. In breve, il codice JavaScript incorporato blocca anche l'analizzatore sintattico.

Detto questo, nonostante il blocco del CSS, l'inserimento in linea dello script rende il rendering della pagina più veloce? Prova e vedi cosa succede.

JavaScript esterno:

DOM, CSSOM, JS

JavaScript incorporato:

DOM, CSSOM e JS incorporato

Stiamo inviando una richiesta in meno, ma i tempi di onload e domContentLoaded sono praticamente gli stessi. Perché? Sappiamo che non importa se JavaScript è incorporato o esterno, perché non appena il browser raggiunge il tag dello script, si blocca e attende fino alla creazione del CSSOM. Inoltre, nel nostro primo esempio, il browser scarica sia CSS che JavaScript in parallelo e il download termina all'incirca contemporaneamente. In questo caso, l'inserimento in linea del codice JavaScript non ci aiuta molto. Esistono però diverse strategie che possono velocizzare il rendering della pagina.

Innanzitutto, ricorda che tutti gli script incorporati bloccano il parser, ma per gli script esterni possiamo aggiungere l'attributo async per sbloccare l'analizzatore sintattico. Annulla la digitazione e prova:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Async</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body onload="measureCRP()">
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script async src="timing.js"></script>
  </body>
</html>

Prova

JavaScript (esterno) che blocca l'interprete:

DOM, CSSOM, JS

JavaScript asincrono (esterno):

DOM, CSSOM, JS asincrono

Molto meglio. L'evento domContentLoaded viene attivato poco dopo l'analisi dell'HTML; il browser sa di non bloccare JavaScript e, poiché non sono presenti altri script di blocco dell'interprete, anche la costruzione del CSSOM può procedere in parallelo.

In alternativa, avremmo potuto inserire in linea sia il CSS che il JavaScript:

<!DOCTYPE html>
<html>
  <head>
    <title>Critical Path: Measure Inlined</title>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <style>
      p {
        font-weight: bold;
      }
      span {
        color: red;
      }
      p span {
        display: none;
      }
      img {
        float: right;
      }
    </style>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

Prova

DOM, CSS in linea, JS in linea

Nota che l'orario di domContentLoaded è effettivamente lo stesso dell'esempio precedente; invece di contrassegnare il codice JavaScript come asincrono, abbiamo incorporato il codice CSS e JS nella pagina stessa. Questo rende la nostra pagina HTML molto più grande, ma il vantaggio è che il browser non deve attendere per recuperare le risorse esterne; tutto è già nella pagina.

Come puoi vedere, anche con una pagina molto semplice, l'ottimizzazione del percorso di rendering critico è un esercizio non banale: dobbiamo comprendere il grafico delle dipendenze tra risorse diverse, identificare quali risorse sono "critiche" e scegliere tra diverse strategie su come includerle nella pagina. Non esiste una sola soluzione a questo problema. ogni pagina è diversa. Occorre seguire autonomamente una procedura simile per individuare la strategia ottimale.

Detto questo, vediamo se possiamo fare un passo indietro e identificare alcuni schemi generali di rendimento.

Pattern di rendimento

La pagina più semplice possibile è costituita solo dal markup HTML; senza CSS, JavaScript o altri tipi di risorse. Per visualizzare questa pagina, il browser deve avviare la richiesta, attendere l'arrivo del documento HTML, analizzarlo, generare il DOM e infine eseguire il rendering sullo schermo:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Critical Path: No Style</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

Prova

CRP Hello World

Il tempo tra T0 e T1 acquisisce i tempi di elaborazione della rete e del server. Nel caso migliore (se il file HTML è di piccole dimensioni), è sufficiente un solo round trip di rete per recuperare l'intero documento. A causa del funzionamento dei protocolli di trasporto TCP, i file di grandi dimensioni potrebbero richiedere più viaggi di andata e ritorno. Di conseguenza, nel migliore dei casi la pagina sopra indicata ha un percorso di rendering critico di andata e ritorno (minimo).

Ora prendi in considerazione la stessa pagina, ma con un file CSS esterno:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
  </body>
</html>

Prova

CRP DOM + CSSOM

Ancora una volta, ci viene inviato un roundtrip di rete per recuperare il documento HTML e quindi il markup recuperato indica che abbiamo bisogno anche del file CSS; Ciò significa che il browser deve tornare al server e recuperare il CSS prima di poter visualizzare la pagina sullo schermo. Di conseguenza, questa pagina richiede un minimo di due round trip prima di poter essere visualizzata. Anche in questo caso, il file CSS può richiedere più round trip, dando l'enfasi al "minimo".

Di seguito sono riportati alcuni termini che utilizziamo per descrivere il percorso di rendering critico:

  • Risorsa critica: risorsa che potrebbe bloccare il rendering iniziale della pagina.
  • Lunghezza del percorso critico:il numero di round trip o il tempo totale necessario per recuperare tutte le risorse critiche.
  • Byte critici: numero totale di byte necessari per ottenere la prima visualizzazione della pagina, ossia la somma delle dimensioni dei file di trasferimento di tutte le risorse critiche. Il nostro primo esempio, con un'unica pagina HTML, conteneva un'unica risorsa critica (il documento HTML); la lunghezza del percorso critico era pari a un roundtrip di rete (supponendo che il file fosse di dimensioni ridotte) e il totale dei byte critici corrispondeva solo alla dimensione di trasferimento del documento HTML stesso.

Ora confronta questo con le caratteristiche del percorso critico dell'esempio HTML e CSS precedente:

DOM + CSSOM CRP

  • 2 risorse critiche
  • 2 o più viaggi di andata e ritorno per la lunghezza minima del percorso critico
  • 9 KB di byte critici

Abbiamo bisogno sia del codice HTML che di CSS per costruire l'albero di rendering. Di conseguenza, sia HTML che CSS sono risorse critiche: il CSS viene recuperato solo dopo che il browser riceve il documento HTML, quindi la lunghezza del percorso critico è di almeno due roundtrip. Entrambe le risorse sommano fino a 9 kB di byte critici.

Ora aggiungi al mix un altro file JavaScript.

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

Prova

Abbiamo aggiunto app.js, che è sia un asset JavaScript esterno nella pagina sia una risorsa di blocco dell'analizzatore sintattico (importante). Peggio ancora, per eseguire il file JavaScript dobbiamo bloccare e attendere CSSOM; ricorda che JavaScript può eseguire query sul CSSOM e, di conseguenza, il browser viene messo in pausa fino al download di style.css e alla creazione del CSSOM.

DOM, CSSOM, CRP JavaScript

Detto questo, nella pratica se osserviamo la "struttura a cascata di rete" di questa pagina, noterai che le richieste CSS e JavaScript vengono avviate quasi contemporaneamente; il browser ottiene il codice HTML, scopre entrambe le risorse e avvia entrambe le richieste. Di conseguenza, la pagina mostrata nell'immagine precedente presenta le seguenti caratteristiche del percorso critico:

  • 3 risorse critiche
  • 2 o più andata e ritorno per la lunghezza minima del percorso critico
  • 11 kB di byte critici

Ora abbiamo tre risorse critiche che aggiungono fino a 11 kB di byte critici, ma la lunghezza del percorso critico è ancora di due round trip perché possiamo trasferire CSS e JavaScript in parallelo. Capire le caratteristiche del percorso di rendering critico significa essere in grado di identificare le risorse critiche e anche di capire in che modo il browser ne programmerà il recupero.

Dopo aver chattato con i nostri sviluppatori del sito, ci rendiamo conto che non è necessario che il codice JavaScript che abbiamo incluso nella nostra pagina bloccasse. abbiamo inserito alcuni dati di analisi e altro codice che non devono bloccare il rendering della nostra pagina. Con queste informazioni, possiamo aggiungere l'attributo async all'elemento <script> per sbloccare il parser:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

Prova

DOM, CSSOM, CRP JavaScript asincrono

Uno script asincrono presenta diversi vantaggi:

  • Lo script non blocca più il parser e non fa parte del percorso di rendering critico.
  • Poiché non esistono altri script critici, il CSS non deve bloccare l'evento domContentLoaded.
  • Prima si attiva l'evento domContentLoaded, prima può iniziare l'esecuzione dell'altra logica dell'applicazione.

Di conseguenza, la nostra pagina ottimizzata ora torna a due risorse critiche (HTML e CSS), con una lunghezza minima del percorso critico di due round trip e un totale di 9 KB di byte critici.

Infine, se il foglio di stile CSS fosse necessario solo per la stampa, quale aspetto avrebbe?

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" media="print" />
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

Prova

DOM, CSS non bloccanti e CRP JavaScript asincrono

Poiché la risorsa style.css viene utilizzata solo per la stampa, il browser non deve bloccarsi per il rendering della pagina. Pertanto, non appena la costruzione del DOM è completata, il browser dispone di informazioni sufficienti per eseguire il rendering della pagina. Di conseguenza, questa pagina ha una sola risorsa critica (il documento HTML) e la lunghezza minima del percorso di rendering critico è un viaggio di andata e ritorno.

Feedback