Analyser les performances critiques du chemin de rendu

Ilya Grigorik
Ilya Grigorik

Pour identifier et résoudre les goulots d'étranglement qui affectent les performances du chemin critique du rendu, vous devez connaître les écueils les plus courants. Découvrons ensemble des tendances de performances courantes qui vous aideront à optimiser vos pages.

En optimisant le chemin critique du rendu, le navigateur peut afficher la page le plus rapidement possible: l'affichage rapide des pages permet d'augmenter l'engagement, le nombre de pages vues et le taux de conversion. Pour réduire le temps qu'un visiteur passe à consulter un écran vide, nous devons optimiser quelles ressources sont chargées et dans quel ordre.

Pour illustrer ce processus, commençons par le cas le plus simple et créons progressivement la page pour y inclure des ressources, des styles et une logique d'application supplémentaires. Au cours du processus, nous optimiserons chaque cas et verrons où les problèmes peuvent survenir.

Jusqu'à présent, nous nous sommes concentrés exclusivement sur ce qui se passe dans le navigateur une fois que la ressource (CSS, JS ou fichier HTML) est disponible pour le traitement. Nous avons ignoré le temps nécessaire pour récupérer la ressource à partir du cache ou du réseau. Partons du principe que:

  • 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>

Essayer

Nous allons commencer par un balisage HTML de base et une seule image. Pas de CSS ni de JavaScript. Ouvrons la timeline du réseau dans les outils pour les développeurs Chrome et inspectons la cascade de ressources obtenue:

CRP

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). Nous n'avons donc besoin que d'un seul 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 passée à attendre le réseau et l'autre à attendre la réponse du serveur.

Lorsque le contenu HTML devient disponible, le navigateur analyse les octets, les convertit en jetons, puis crée l'arborescence DOM. Notez que les outils de développement indiquent facilement l'heure de l'événement "DOMContentLoaded" en bas (216 ms), 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, quelques millisecondes seulement).

Notez que notre "super photo" n'a pas bloqué l'événement domContentLoaded. Il s'avère que nous pouvons créer l'arborescence de rendu et même peindre la page sans attendre chaque asset de la page: toutes les ressources ne sont pas essentielles pour fournir le premier rendu visuel le plus rapide. En fait, lorsque nous parlons du chemin critique du rendu, nous faisons généralement référence au balisage HTML, CSS et JavaScript. Les images ne bloquent pas l'affichage initial de la page, mais nous devrions également essayer de faire peindre les images dès que possible.

Cela dit, l'événement load (aussi appelé onload) est bloqué sur l'image: les outils de développement signalent l'événement onload à 335 ms. N'oubliez pas que l'événement onload indique le moment auquel toutes les ressources dont la page a besoin ont été téléchargées et traitées. À ce stade, l'icône de chargement peut arrêter de tourner dans le navigateur (la ligne verticale rouge dans la cascade d'annonces).

Combiner JavaScript et CSS

Notre page "Hello World Experience" semble simple, mais beaucoup de choses se passent en arrière-plan. En pratique, il nous faut plus que le code HTML: il y a de fortes chances que nous ayons une feuille de style CSS et un ou plusieurs scripts pour ajouter de l'interactivité à notre page. Ajoutons ces deux éléments et voyons 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>

Essayer

Avant d'ajouter du code JavaScript et CSS:

CRP DOM

Avec JavaScript et CSS:

DOM, CSSOM, JS

L'ajout de fichiers CSS et JavaScript externes entraîne l'ajout de deux requêtes supplémentaires à notre cascade d'annonces, qui sont toutes envoyées à peu près en même temps par le navigateur. Toutefois, notez que la différence de durée est désormais beaucoup plus faible entre les événements domContentLoaded et onload.

Que s'est-il passé ?

  • Contrairement à notre exemple HTML brut, nous devons également extraire et analyser le fichier CSS pour construire le CSSOM. Nous avons besoin à la fois du DOM et du CSSOM pour créer l'arborescence de rendu.
  • Étant donné que 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é. JavaScript pouvant interroger CSSOM, nous devons bloquer le fichier CSS jusqu'à ce qu'il soit téléchargé avant de pouvoir exécuter JavaScript.

Que se passe-t-il si nous remplaçons notre script externe par un script intégré ? Même si le script est directement intégré à la page, le navigateur ne peut pas l'exécuter tant que le CSSOM n'a pas été créé. En bref, le code JavaScript intégré bloque également l'analyseur.

Cela dit, malgré le blocage dans CSS, l'intégration du script accélère-t-elle l'affichage de la page ? Essayons et voyons ce qui se passe.

JavaScript externe:

DOM, CSSOM, JS

JavaScript intégré:

DOM, CSSOM et JavaScript intégré

Nous faisons une requête de moins, mais les valeurs onload et domContentLoaded sont en réalité les mêmes. Pourquoi ? Nous savons que le code JavaScript intégré ou externe n'a pas d'importance, car dès que le navigateur atteint le tag de script, il se bloque et attend que le CSSOM soit construit. En outre, dans le premier exemple, le navigateur télécharge à la fois CSS et JavaScript, et le téléchargement se termine à peu près au même moment. Dans ce cas, l'intégration du code JavaScript ne nous est pas d'une grande utilité. Il existe toutefois plusieurs stratégies pour accélérer l'affichage de notre page.

Tout d'abord, rappelez-vous que tous les scripts intégrés bloquent l'analyseur. Toutefois, pour les scripts externes, nous pouvons ajouter le mot clé "async" afin de débloquer l'analyseur. Annulons l'intégration et essayons le résultat suivant:

<!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>

Essayer

Code JavaScript (externe) bloquant l'analyseur:

DOM, CSSOM, JS

JavaScript asynchrone (externe) :

DOM, CSSOM, JavaScript asynchrone

Bien mieux ! L'événement domContentLoaded se déclenche peu de temps après l'analyse du code HTML. Le navigateur sait qu'il ne faut pas bloquer sur JavaScript, et comme aucun autre analyseur ne bloque les scripts, la construction CSSOM peut également se dérouler en parallèle.

Sinon, nous aurions pu incorporer à la fois 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>

Essayer

DOM, CSS intégré, JS intégré

Notez que l'heure de domContentLoaded est en fait la 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 JS dans la page elle-même. Notre page HTML est ainsi beaucoup plus grande, mais l'avantage est que le navigateur n'a pas besoin d'attendre pour récupérer des ressources externes ; tout est là dans la page.

Comme vous pouvez le constater, même avec une page très simple, l'optimisation du chemin critique du rendu n'est pas un mince exercice: il est nécessaire de comprendre le graphique de dépendance entre différentes ressources, d'identifier celles qui sont "essentielles" et de choisir entre différentes stratégies pour inclure ces ressources sur la page. Il n'existe pas de solution unique à ce problème, chaque page est différente. Vous devez suivre un processus similaire pour déterminer la stratégie optimale.

Toutefois, 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 ne comporte que le balisage HTML ; pas de CSS, pas de JavaScript, ni d'autres types de ressources. Pour afficher cette page, le navigateur doit lancer la requête, attendre que le document HTML arrive, 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>

Essayer

CRP Hello World

L'heure entre T0 et T1 capture les 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 extrait l'intégralité du document. En raison du fonctionnement des protocoles de transport TCP, les fichiers plus volumineux peuvent nécessiter davantage d'allers-retours. Par conséquent, dans le meilleur des cas, la page ci-dessus comporte un chemin critique d'affichage aller-retour (minimum).

Prenons à présent 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>

Essayer

CRP DOM + CSSOM

Là encore, nous effectuons un aller-retour sur le réseau pour récupérer le document HTML, puis le balisage récupéré nous indique que nous avons également besoin du fichier CSS. Cela signifie que le navigateur doit retourner sur le serveur et obtenir le CSS avant de pouvoir afficher la page à l'écran. Par conséquent, cette page entraîne un minimum de deux allers-retours avant de pouvoir être affichée. Là encore, le fichier CSS peut nécessiter plusieurs allers-retours, d'où l'accent mis sur le "minimum".

Définissons le vocabulaire que nous utilisons pour décrire le chemin critique du rendu:

  • Ressource critique:ressource susceptible de bloquer l'affichage initial de la page.
  • Longueur du chemin critique:nombre d'allers-retours ou temps total nécessaire pour récupérer toutes les ressources critiques.
  • Octets critiques:nombre total d'octets requis pour le premier affichage de la page, qui correspond à la somme des tailles de fichiers transférées de toutes les ressources critiques. Notre premier exemple, avec une seule page HTML, contenait 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 petit), et le nombre total d'octets critiques correspondait à la taille de transfert du document HTML lui-même.

Comparons maintenant ce point avec les caractéristiques de chemin critique de l'exemple HTML + CSS ci-dessus:

CRP DOM + CSSOM

  • 2 ressources critiques
  • 2 allers-retours ou plus pour la longueur minimale du chemin critique
  • 9 Ko d'octets critiques

Nous avons besoin de code HTML et CSS pour créer l'arborescence de rendu. Par conséquent, HTML et CSS sont des ressources essentielles: le CSS est récupéré uniquement après que le navigateur a obtenu le document HTML, d'où la longueur du chemin critique est d'au moins deux allers-retours. Les deux ressources s'additionnent pour un total de 9 Ko d'octets critiques.

Ajoutons maintenant un fichier JavaScript supplémentaire au 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>

Essayer

Nous avons ajouté app.js, qui est à la fois un élément JavaScript externe sur la page et une ressource de blocage (essentielle) pour l'analyseur. Pire encore, pour exécuter le fichier JavaScript, nous devons bloquer et attendre CSSOM. Rappelez-vous que JavaScript peut interroger CSSOM. Par conséquent, le navigateur se met en pause jusqu'à ce que style.css soit téléchargé et que CSSOM soit créé.

CRP DOM, CSSOM et JavaScript

Cela dit, dans la pratique, si nous examinons la "cascade d'annonces" de cette page, vous constaterez que les requêtes CSS et JavaScript sont lancées à peu près en même temps. Le navigateur obtient le code HTML, découvre les deux ressources et lance les deux requêtes. Par conséquent, la page ci-dessus présente les caractéristiques de chemin critique suivantes:

  • 3 ressources critiques
  • 2 allers-retours ou plus pour la longueur minimale du chemin critique
  • 11 Ko d'octets critiques

Nous disposons désormais de trois ressources critiques qui totalisent jusqu'à 11 Ko d'octets critiques, mais la longueur de notre chemin critique est toujours de deux allers-retours, car nous pouvons transférer le CSS et le JavaScript en parallèle. Pour déterminer les caractéristiques de votre chemin critique de rendu, vous devez être capable d'identifier les ressources critiques et de comprendre comment le navigateur planifiera leurs récupérations. Continuons avec notre exemple.

Après avoir discuté avec les développeurs de notre site, nous nous rendons compte que le code JavaScript que nous avons inclus dans notre page n'a pas besoin d'être bloqué. Nous y avons des données analytiques et d'autres codes qui n'ont pas besoin de bloquer l'affichage de notre page. Avec ces connaissances, nous pouvons ajouter l'attribut "async" au tag de 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>

Essayer

DOM, CSSOM, CRP JavaScript asynchrone

Un script asynchrone présente plusieurs avantages:

  • Le script ne bloque plus l'analyseur et ne fait plus partie du chemin critique du rendu.
  • Comme il n'existe aucun autre script critique, le CSS n'a pas besoin de bloquer l'événement domContentLoaded.
  • Plus vite l'événement domContentLoaded se déclenche, plus vite une autre logique d'application peut commencer à s'exécuter.

Par conséquent, notre page optimisée est maintenant à nouveau sur deux ressources critiques (HTML et CSS), avec une longueur de chemin critique minimale de deux allers-retours, 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>

Essayer

DOM, CSS non bloquant et CRP JavaScript asynchrone

É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. Ainsi, dès que la construction du DOM est terminée, le navigateur dispose de suffisamment d'informations pour afficher la page. Cette page ne contient donc qu'une seule ressource critique (le document HTML), et la longueur minimale du chemin de rendu critique est d'un aller-retour.

Commentaires