Publié le 31 mars 2014
Pour identifier et résoudre les goulots d'étranglement de performances du chemin de rendu critique, vous devez bien connaître les écueils courants. Une visite guidée pour identifier les tendances de performances courantes vous aidera à optimiser vos pages.
L'optimisation du chemin de rendu critique permet au navigateur de peindre la page aussi rapidement que possible. Des pages plus rapides améliorent l'engagement, le nombre de pages vues et les conversions. Pour réduire le temps pendant lequel un visiteur voit un écran vide, nous devons optimiser les ressources chargées et l'ordre dans lequel elles le sont.
Pour illustrer ce processus, commençons par le cas le plus simple possible et développons notre page de manière incrémentielle pour inclure des ressources, des styles et une logique d'application supplémentaires. Nous allons optimiser chaque cas et identifier les points où des erreurs peuvent se produire.
Jusqu'à présent, nous nous sommes concentrés exclusivement sur ce qui se passe dans le navigateur une fois que la ressource (fichier CSS, JS ou HTML) est disponible pour le traitement. Nous avons ignoré le temps nécessaire pour extraire la ressource à partir du cache ou du réseau. Nous allons supposer les points suivants:
- Un aller-retour réseau (latence de propagation) vers le serveur coûte 100 ms.
- Le temps de réponse du serveur est de 100 ms pour le document HTML et de 10 ms pour tous les autres fichiers.
L'expérience "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>
Commencez avec une balise HTML de base et une seule image, sans CSS ni JavaScript. Ouvrez ensuite le panneau "Network" (Réseau) dans Chrome DevTools et inspectez la cascade de ressources qui s'affiche:
Comme prévu, le téléchargement du fichier HTML a pris environ 200 ms. Notez que la partie transparente de la ligne bleue représente la durée pendant laquelle le navigateur attend sur le réseau sans recevoir d'octets de réponse, tandis que la partie pleine indique le temps nécessaire pour terminer le téléchargement après la réception des premiers octets de réponse. Le téléchargement HTML est minuscule (<4 ko). Il nous suffit donc d'un aller-retour pour récupérer le fichier complet. Par conséquent, l'extraction du document HTML prend environ 200 ms, la moitié du temps étant consacrée à l'attente sur le réseau et l'autre moitié à l'attente de la réponse du serveur.
Lorsque le contenu HTML est disponible, le navigateur analyse les octets, les convertit en jetons et crée l'arborescence DOM. Notez que DevTools indique de manière pratique l'heure de l'événement DOMContentLoaded en bas (216 ms), ce qui correspond également à la ligne verticale bleue. L'écart entre la fin du téléchargement HTML et la ligne verticale bleue (DOMContentLoaded) correspond au temps nécessaire au navigateur pour créer l'arborescence DOM. Dans ce cas, il ne s'agit que de quelques millisecondes.
Notez que notre "superbe photo" n'a pas bloqué l'événement domContentLoaded
. Il s'avère que nous pouvons construire l'arborescence de rendu et même peindre la page sans attendre chaque composant de la page: toutes les ressources ne sont pas essentielles pour obtenir la première peinture rapide. En fait, lorsque nous parlons du chemin de rendu critique, nous faisons généralement référence au balisage HTML, au CSS et au JavaScript. Les images ne bloquent pas le rendu initial de la page, mais nous devrions également essayer de les peindre dès que possible.
Toutefois, l'événement load
(également appelé onload
) est bloqué sur l'image: DevTools signale l'événement onload
à 335 ms. Rappelez-vous que l'événement onload
marque le point auquel toutes les ressources requises par la page ont été téléchargées et traitées. À ce stade, la roue de chargement peut s'arrêter de tourner dans le navigateur (ligne verticale rouge dans la cascade).
Ajouter du code JavaScript et CSS
Notre page "Expérience Hello World" semble basique, mais de nombreux éléments se cachent sous le capot. En pratique, nous aurons besoin de plus que du code HTML: il est probable que nous ayons une feuille de style CSS et un ou plusieurs scripts pour ajouter de l'interactivité à notre page. Ajoutez les deux à la combinaison pour voir ce qui se passe:
<!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>
Avant d'ajouter du code JavaScript et CSS:
Avec JavaScript et CSS:
L'ajout de fichiers CSS et JavaScript externes ajoute deux requêtes supplémentaires à notre cascade, que le navigateur distribue toutes à peu près en même temps. Toutefois, notez qu'il existe désormais une différence de synchronisation beaucoup plus faible entre les événements domContentLoaded
et onload
.
Que s'est-il passé ?
- Contrairement à notre exemple HTML simple, nous devons également extraire et analyser le fichier CSS pour créer le CSSOM. Nous avons besoin à la fois du DOM et du CSSOM pour créer l'arborescence de rendu.
- Comme la page contient également un fichier JavaScript bloquant l'analyseur, l'événement
domContentLoaded
est bloqué jusqu'à ce que le fichier CSS soit téléchargé et analysé. Comme le code JavaScript peut interroger le CSSOM, nous devons bloquer le fichier CSS jusqu'à ce qu'il soit téléchargé avant de pouvoir exécuter le code JavaScript.
Que se passe-t-il si nous remplaçons notre script externe par un script intégré ? Même si le script est intégré directement à la page, le navigateur ne peut pas l'exécuter tant que le CSSOM n'est pas créé. En résumé, le code JavaScript intégré est également bloquant pour l'analyseur.
Cela dit, malgré le blocage du CSS, l'intégration du script accélère-t-elle l'affichage de la page ? Essayez et voyez ce qui se passe.
JavaScript externe :
JavaScript intégré :
Nous effectuons une requête de moins, mais les temps onload
et domContentLoaded
sont effectivement les mêmes. Pourquoi ? Nous savons qu'il n'a pas d'importance si le code JavaScript est intégré ou externe, car dès que le navigateur rencontre la balise de script, il se bloque et attend que le CSSOM soit créé. De plus, dans notre premier exemple, le navigateur télécharge à la fois le CSS et le JavaScript en parallèle, et le téléchargement se termine à peu près en même temps. Dans ce cas, l'intégration du code JavaScript ne nous aide pas beaucoup. Cependant, il existe plusieurs stratégies qui peuvent accélérer l'affichage de notre page.
Tout d'abord, rappelez-vous que tous les scripts intégrés bloquent l'analyseur, mais que pour les scripts externes, vous pouvez ajouter l'attribut async
pour débloquer l'analyseur. Annulez l'intégration et essayez:
<!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 bloquant l'analyseur (externe) :
JavaScript asynchrone (externe) :
C'est beaucoup mieux ! L'événement domContentLoaded
se déclenche peu de temps après l'analyse du code HTML. Le navigateur sait qu'il ne doit pas bloquer sur JavaScript et, comme il n'y a pas d'autres scripts bloquant l'analyseur, la construction du CSSOM peut également se poursuivre en parallèle.
Nous aurions également pu inliner le CSS et le 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>
Notez que le temps domContentLoaded
est effectivement le même que dans l'exemple précédent. Au lieu de marquer notre code JavaScript comme asynchrone, nous avons intégré le CSS et le code JavaScript dans la page elle-même. Cela rend notre page HTML beaucoup plus volumineuse, mais l'avantage est que le navigateur n'a pas à attendre pour extraire des ressources externes. Tout est disponible sur la page.
Comme vous pouvez le constater, même avec une page très basique, optimiser le chemin de rendu critique est un exercice complexe: nous devons comprendre le graphique des dépendances entre les différentes ressources, identifier les ressources "critiques" et choisir parmi différentes stratégies pour inclure ces ressources sur la page. Il n'existe pas de solution unique à ce problème, car chaque page est différente. Vous devez suivre un processus similaire par vous-même pour déterminer la stratégie optimale.
Cela dit, voyons si nous pouvons prendre du recul et identifier des tendances générales de performances.
Modèles de performances
La page la plus simple possible ne contient que du balisage HTML, sans CSS, JavaScript ni autres types de ressources. Pour afficher cette page, le navigateur doit lancer la requête, attendre l'arrivée du document HTML, l'analyser, créer le DOM, puis l'afficher à l'écran:
<!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>
Le temps écoulé entre T0 et T1 correspond aux temps de traitement du réseau et du serveur. Dans le meilleur des cas (si le fichier HTML est petit), un seul aller-retour réseau suffit à extraire l'intégralité du document. En raison du fonctionnement des protocoles de transport TCP, les fichiers volumineux peuvent nécessiter davantage d'allers-retours. Par conséquent, dans le meilleur des cas, la page ci-dessus comporte un chemin de rendu critique aller-retour (minimum).
Prenons maintenant la même page, mais avec un fichier CSS externe:
<!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>
Une fois de plus, nous effectuons un aller-retour réseau pour récupérer le document HTML. Le balisage récupéré nous indique alors que nous avons également besoin du fichier CSS. Cela signifie que le navigateur doit revenir sur le serveur et récupérer le CSS avant de pouvoir afficher la page à l'écran. Par conséquent, cette page nécessite au moins deux aller-retour avant de pouvoir être affichée. Encore une fois, le fichier CSS peut nécessiter plusieurs aller-retour, d'où l'accent mis sur le "minimum".
Voici quelques termes que nous utilisons pour décrire le chemin de rendu critique:
- Ressource critique:ressource pouvant bloquer le rendu initial de la page.
- Longueur du chemin critique:nombre d'allers-retours ou temps total nécessaire pour extraire toutes les ressources critiques.
- Octets critiques:nombre total d'octets requis pour obtenir le premier rendu de la page, soit la somme des tailles de fichier de transfert de toutes les ressources critiques. Notre premier exemple, avec une seule page HTML, ne contenait qu'une seule ressource critique (le document HTML). La longueur du chemin critique était également égale à un aller-retour réseau (en supposant que le fichier était de petite taille), et le nombre total d'octets critiques ne représentait que la taille de transfert du document HTML lui-même.
Comparez maintenant ces caractéristiques à celles du chemin critique de l'exemple HTML et CSS précédent:
- 2 ressources critiques
- Deux aller-retour ou plus pour la longueur minimale du chemin critique
- 9 ko d'octets critiques
Nous avons besoin à la fois du code HTML et du code CSS pour construire l'arborescence de rendu. Par conséquent, le code HTML et le code CSS sont des ressources critiques: le code CSS n'est récupéré qu'après que le navigateur a obtenu le document HTML. Par conséquent, la longueur du chemin critique est d'au moins deux aller-retour. Les deux ressources représentent un total de 9 Ko d'octets critiques.
Ajoutez maintenant un fichier JavaScript supplémentaire.
<!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>
Nous avons ajouté app.js
, qui est à la fois un composant JavaScript externe sur la page et une ressource bloquante (c'est-à-dire critique) pour l'analyseur. Pire encore, pour exécuter le fichier JavaScript, nous devons bloquer et attendre le CSSOM. N'oubliez pas que JavaScript peut interroger le CSSOM. Par conséquent, le navigateur est mis en pause jusqu'à ce que style.css
soit téléchargé et que le CSSOM soit créé.
Toutefois, en pratique, si nous examinons la "cascade réseau" de cette page, vous constaterez que les requêtes CSS et JavaScript sont lancées à peu près en même temps. Le navigateur récupère le code HTML, découvre les deux ressources et lance les deux requêtes. Par conséquent, la page illustrée dans l'image précédente présente les caractéristiques suivantes du chemin critique:
- 3 ressources critiques
- Deux aller-retours ou plus pour la longueur minimale du chemin critique
- 11 ko d'octets critiques
Nous avons maintenant trois ressources critiques qui totalisent 11 ko d'octets critiques, mais la longueur du chemin critique reste de deux aller-retour, car nous pouvons transférer le CSS et le JavaScript en parallèle. Déterminer les caractéristiques de votre chemin de rendu critique signifie être en mesure d'identifier les ressources critiques et de comprendre comment le navigateur planifiera leurs récupérations.
Après avoir discuté avec les développeurs de notre site, nous avons réalisé que le code JavaScript que nous avons inclus sur notre page n'avait pas besoin d'être bloquant. Nous avons inclus des analyses et d'autres codes qui n'ont pas besoin de bloquer le rendu de notre page. Avec ces informations, nous pouvons ajouter l'attribut async
à l'élément <script>
pour débloquer l'analyseur:
<!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>
Un script asynchrone présente plusieurs avantages:
- Le script n'est plus bloquant pour l'analyseur et ne fait plus partie du chemin de rendu critique.
- Étant donné qu'il n'y a pas d'autres scripts critiques, le CSS n'a pas besoin de bloquer l'événement
domContentLoaded
. - Plus l'événement
domContentLoaded
se déclenche tôt, plus l'exécution d'une autre logique d'application peut commencer rapidement.
Notre page optimisée comporte désormais deux ressources critiques (HTML et CSS), avec une longueur minimale du chemin critique de deux aller-retour et un total de 9 ko d'octets critiques.
Enfin, si la feuille de style CSS n'était nécessaire que pour l'impression, à quoi ressemblerait-elle ?
<!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>
Étant donné que la ressource style.css n'est utilisée que pour l'impression, le navigateur n'a pas besoin de la bloquer pour afficher la page. Par conséquent, dès que la création du DOM est terminée, le navigateur dispose d'informations suffisantes pour afficher la page. Par conséquent, cette page ne comporte qu'une seule ressource critique (le document HTML), et la longueur minimale du chemin de rendu critique est d'un aller-retour.