Data di pubblicazione: 31 marzo 2014
L'identificazione e la risoluzione dei colli di bottiglia critici del percorso di rendering richiedono una buona conoscenza dei problemi 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: pagine più veloci si traducono in un maggiore coinvolgimento, un maggior numero di pagine visualizzate e una conversione migliorata. 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 quanto segue:
- 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>
Inizia con un markup HTML di base e una singola immagine, senza CSS o JavaScript. Quindi, apri il riquadro Rete in Chrome DevTools e controlla la struttura a cascata delle risorse risultante:
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, con 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
. In realtà, possiamo costruire la struttura di rendering e persino dipingere la pagina senza attendere ogni risorsa presente al suo interno: non tutte le risorse sono fondamentali per ottenere una prima visualizzazione rapida. Infatti, quando parliamo del percorso di rendering critico, in genere ci riferiamo al markup HTML, al 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 sull'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 canalizzazione).
Aggiunta di JavaScript e CSS
La nostra pagina "Esperienza Ciao mondo" sembra semplice, 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. Aggiungi 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>
Prima di aggiungere JavaScript e CSS:
Con JavaScript e CSS:
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 del nostro esempio HTML semplice, dobbiamo anche recuperare e analizzare il file CSS per costruire il CSSOM e abbiamo bisogno sia del DOM che del CSSOM per costruire la struttura 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 sostituiamo lo script esterno con uno script in linea? Anche se lo script è inserito direttamente nella pagina, il browser non può eseguirlo finché non viene creato il CSSOM. In breve, anche il codice JavaScript in linea blocca il parser.
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:
JavaScript in linea:
Stiamo inviando una richiesta in meno, ma i tempi di onload
e domContentLoaded
sono praticamente gli stessi. Perché? Sappiamo che non importa se il codice JavaScript è in linea o esterno, perché non appena il browser incontra il tag script, si blocca e attende la compilazione del CSSOM. Inoltre, nel nostro primo esempio, il browser scarica sia CSS che JavaScript in parallelo e il download termina all'incirca nello stesso momento. 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 in linea bloccano il parser, ma per gli script esterni possiamo aggiungere l'attributo async
per sbloccare il parser. Annulla l'inserimento in linea 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>
JavaScript (esterno) che blocca l'interprete:
JavaScript asincrono (esterno):
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, la costruzione del CSSOM può procedere anche in parallelo.
In alternativa, avremmo potuto inserire in linea sia il CSS sia 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>
Tieni presente che il tempo domContentLoaded
è praticamente lo stesso dell'esempio precedente; anziché contrassegnare il codice JavaScript come asincrono, abbiamo inserito in linea sia il CSS sia il codice JavaScript 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 soluzione unica a questo problema, perché ogni pagina è diversa. Per trovare la strategia ottimale, devi seguire una procedura simile in autonomia.
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; niente CSS, JavaScript o altri tipi di risorse. Per visualizzare questa pagina, il browser deve avviare la richiesta, attendere l'arrivo del documento HTML, analizzarlo, creare il DOM e infine visualizzarlo 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>
Il tempo tra T0 e T1 acquisisce i tempi di elaborazione della rete e del server. Nel migliore dei casi (se il file HTML è di piccole dimensioni), l'intero documento viene recuperato con un solo viaggio di andata e ritorno sulla rete. 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 (minima).
Ora considera 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>
Ancora una volta, si verifica un viaggio di andata e ritorno sulla rete per recuperare il documento HTML, quindi il markup recuperato ci dice che abbiamo bisogno anche del file CSS; ciò significa che il browser deve tornare al server e recuperare il CSS prima di poter eseguire il rendering della pagina sullo schermo. Di conseguenza, questa pagina comporta almeno due round trip prima di poter essere visualizzata. Ancora una volta, il file CSS potrebbe richiedere più viaggi di andata e ritorno, da qui l'enfasi su "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:numero di viaggi di andata e ritorno o tempo totale necessario per recuperare tutte le risorse critiche.
- Byte critici:numero totale di byte necessari per arrivare al primo rendering della pagina, ovvero la somma delle dimensioni dei file di trasferimento di tutte le risorse critiche. Il nostro primo esempio, con una singola pagina HTML, conteneva una singola risorsa critica (il documento HTML); la lunghezza del percorso critico era uguale anche a un viaggio di andata e ritorno della rete (supponendo che il file fosse di piccole dimensioni) e i byte critici totali erano solo le dimensioni di trasferimento del documento HTML stesso.
Ora confronta questo con le caratteristiche del percorso critico dell'esempio HTML e CSS precedente:
- 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 di HTML che di CSS per costruire la struttura 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 aggiungono un totale di 9 KB di byte critici.
Ora aggiungi un altro file JavaScript al mix.
<!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>
Abbiamo aggiunto app.js
, che è sia un asset JavaScript esterno sulla pagina sia una risorsa di blocco del parser (ovvero critica). Peggio ancora, per eseguire il file JavaScript dobbiamo bloccare e attendere il CSSOM; ricorda che JavaScript può eseguire query sul CSSOM e quindi il browser si mette in pausa fino al download di style.css
e alla creazione del CSSOM.
Detto questo, in pratica, se esaminiamo la "cascata di rete" di questa pagina, vedrai che sia le richieste CSS che quelle JavaScript vengono avviate all'incirca nello stesso momento; il browser riceve il codice HTML, rileva 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 fondamentali
- 2 o più viaggi di 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 roundtrip perché possiamo trasferire il codice 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 gli sviluppatori del nostro sito, ci siamo resi conto che il codice JavaScript che abbiamo incluso nella nostra pagina non deve essere bloccato. Abbiamo alcuni dati 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>
Uno script asincrono presenta diversi vantaggi:
- Lo script non blocca più il parser e non fa parte del percorso di rendering critico.
- Poiché non sono presenti altri script critici, il CSS non deve bloccare l'evento
domContentLoaded
. - Prima viene attivato l'evento
domContentLoaded
, prima può iniziare l'esecuzione dell'altra logica di applicazione.
Di conseguenza, la nostra pagina ottimizzata ora è tornata 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, come sarebbe?
<!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>
Poiché la risorsa style.css viene utilizzata solo per la stampa, il browser non deve bloccarsi per visualizzare la 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.