Découvrez ce qu'est l'outil d'analyse de préchargement du navigateur, comment il améliore les performances et comment éviter les distractions.
Pour optimiser la vitesse des pages, il est négligé de bien connaître les composants internes du navigateur. Les navigateurs effectuent certaines optimisations pour améliorer les performances, ce que nous ne pouvons pas faire en tant que développeur, mais tant que ces optimisations ne sont pas involontairement entravées.
Une optimisation interne du navigateur à comprendre est l'outil d'analyse de préchargement du navigateur. Cet article explique le fonctionnement de l'outil d'analyse du préchargement et, plus important encore, comment éviter les perturbations.
Qu'est-ce qu'un scanner de préchargement ?
Chaque navigateur dispose d'un analyseur HTML principal qui tokenise le balisage brut et le traite dans un modèle d'objet. Tout se poursuit jusqu'à ce que l'analyseur s'arrête lorsqu'il trouve une ressource bloquante, telle qu'une feuille de style chargée avec un élément <link>
ou un script chargé avec un élément <script>
sans attribut async
ou defer
.
Dans le cas des fichiers CSS, l'analyse et l'affichage sont bloqués afin d'éviter un flash de contenu sans style (FOUC, unstyled content), c'est-à-dire lorsqu'une version sans style d'une page peut être affichée brièvement avant que des styles ne lui soient appliqués.
Le navigateur bloque également l'analyse et l'affichage de la page lorsqu'il rencontre des éléments <script>
sans attribut defer
ou async
.
En effet, le navigateur ne peut pas savoir avec certitude si un script donné modifiera le DOM alors que l'analyseur HTML principal est encore en train de faire son travail. C'est pourquoi il est courant de charger votre code JavaScript à la fin des documents de sorte que les effets du blocage de l'analyse et de l'affichage deviennent négligeables.
Ce sont de bonnes raisons pour lesquelles le navigateur devrait bloquer à la fois l'analyse et l'affichage. Pourtant, il n'est pas souhaitable de bloquer ces étapes importantes, car elles risquent de retarder la diffusion en retardant la découverte d'autres ressources importantes. Heureusement, les navigateurs font de leur mieux pour atténuer ces problèmes grâce à un analyseur HTML secondaire appelé outil d'analyse de préchargement.
Le rôle d'un analyseur de préchargement est spéculatif. En d'autres termes, il examine le balisage brut afin de trouver les ressources à extraire avec opportunisme avant que l'analyseur HTML principal ne les découvre autrement.
Comment savoir quand l'outil d'analyse du préchargement fonctionne ?
L'outil d'analyse du préchargement existe en raison d'un blocage de l'affichage et de l'analyse. Si ces deux problèmes de performances n'existent jamais, l'outil d'analyse de préchargement ne serait pas très utile. Pour savoir si une page Web peut bénéficier de l'outil d'analyse de préchargement, il faut tenir compte de ces phénomènes bloquants. Pour ce faire, vous pouvez introduire un délai artificiel pour les requêtes visant à déterminer où fonctionne l'outil d'analyse de préchargement.
Prenons cette page de texte et d'images de base avec une feuille de style comme exemple. Étant donné que les fichiers CSS bloquent à la fois l'affichage et l'analyse, vous introduisez un délai artificiel de deux secondes pour la feuille de style via un service proxy. Ce délai permet de voir plus facilement dans la cascade du réseau où fonctionne l'outil d'analyse du préchargement.
Comme vous pouvez le voir dans la cascade d'annonces, l'outil d'analyse du préchargement détecte l'élément <img>
même si l'affichage et l'analyse du document sont bloqués. Sans cette optimisation, le navigateur ne peut pas récupérer les éléments avec opportunisme pendant la période de blocage, et davantage de demandes de ressources seraient consécutives plutôt que simultanées.
Maintenant que cet exemple de jouet est terminé, examinons quelques schémas concrets qui permettent de contourner le scanner de préchargement et comment y remédier.
Scripts async
injectés
Imaginons que votre <head>
comporte du code HTML incluant du code JavaScript intégré, comme ceci:
<script>
const scriptEl = document.createElement('script');
scriptEl.src = '/yall.min.js';
document.head.appendChild(scriptEl);
</script>
Les scripts injectés sont async
par défaut. Par conséquent, lorsqu'ils sont injectés, ils se comportent comme si l'attribut async
leur avait été appliqué. Cela signifie qu'elle s'exécutera dès que possible et ne bloquera pas l'affichage. Cela semble optimal, non ? Pourtant, si vous partez du principe que cette <script>
intégrée vient après un élément <link>
qui charge un fichier CSS externe, vous obtenez un résultat non optimal:
Analysons ce qui s'est passé ici:
- Au bout de 0 seconde, le document principal est demandé.
- Au bout de 1,4 seconde, le premier octet de la requête de navigation arrive.
- Au bout de 2 secondes, le fichier CSS et l'image sont demandés.
- Étant donné que l'analyseur est bloqué lors du chargement de la feuille de style et que le code JavaScript intégré qui injecte le script
async
apparaît après cette feuille de style au bout de 2,6 secondes, la fonctionnalité fournie par le script n'est pas disponible dès qu'elle le pourrait.
Ce comportement n'est pas optimal, car le script n'est demandé qu'une fois le téléchargement de la feuille de style terminé. Cela retarde l'exécution du script dès que possible. En revanche, comme l'élément <img>
est visible dans le balisage fourni par le serveur, il est découvert par l'outil d'analyse de préchargement.
Que se passe-t-il si vous utilisez une balise <script>
standard avec l'attribut async
au lieu d'injecter le script dans le DOM ?
<script src="/yall.min.js" async></script>
Voici le résultat:
Vous serez peut-être tenté de dire que vous pouvez résoudre ces problèmes en utilisant rel=preload
. Cela fonctionnerait certainement, mais cela peut avoir des effets secondaires. Après tout, pourquoi utiliser rel=preload
pour résoudre un problème qui peut être évité en n'injectant pas d'élément <script>
dans le DOM ?
Le préchargement résout le problème ici, mais cela introduit un nouveau problème: le script async
des deux premières démonstrations, bien qu'il soit chargé dans <head>
, est chargé avec la priorité "Faible", tandis que la feuille de style est chargée à la priorité "Plus élevée". Dans la dernière démonstration où le script async
était préchargé, la feuille de style est toujours chargée avec la priorité la plus élevée, mais la priorité du script est passée à "Élevée".
Lorsque la priorité d'une ressource est élevée, le navigateur lui alloue davantage de bande passante. Cela signifie que, même si la feuille de style a la priorité la plus élevée, la priorité élevée du script peut provoquer des conflits dans la bande passante. Cela peut être dû à la lenteur des connexions ou à des ressources très importantes.
La réponse ici est simple: si un script est nécessaire au démarrage, ne neutralisez pas l'outil d'analyse de préchargement en l'injectant dans le DOM. Si nécessaire, testez l'emplacement de l'élément <script>
, ainsi que des attributs tels que defer
et async
.
Chargement différé avec JavaScript
Le chargement différé est une excellente méthode de conservation des données, souvent appliquée aux images. Cependant, il arrive que le chargement différé s'applique à tort aux images qui se trouvent dans la partie au-dessus de la ligne de flottaison.
Cela entraîne des problèmes potentiels de visibilité des ressources en lien avec l'outil d'analyse de préchargement, et peut retarder inutilement le temps nécessaire pour découvrir une référence à une image, la télécharger, la décoder et la présenter. Prenons l'exemple de ce balisage d'image:
<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
L'utilisation d'un préfixe data-
est un modèle courant dans les chargeurs différés basés sur JavaScript. Lorsque l'utilisateur fait défiler l'image dans la fenêtre d'affichage, le chargeur différé supprime le préfixe data-
. Ainsi, dans l'exemple précédent, data-src
devient src
. Cette mise à jour invite le navigateur à récupérer la ressource.
Ce schéma ne pose pas de problème tant qu'il n'est pas appliqué aux images qui se trouvent dans la fenêtre d'affichage au démarrage. Étant donné que l'analyseur de préchargement ne lit pas l'attribut data-src
de la même manière qu'un attribut src
(ou srcset
), la référence d'image n'a pas été découverte précédemment. Pire encore, le chargement de l'image est retardé après le téléchargement, la compilation et l'exécution du code JavaScript du chargeur différé.
En fonction de la taille de l'image (qui peut dépendre de la taille de la fenêtre d'affichage), elle peut être adaptée au Largest Contentful Paint (LCP). Lorsque le scanner de préchargement ne peut pas extraire de manière spéculative la ressource image à l'avance, peut-être au moment où la ou les feuilles de style de la page bloquent l'affichage, le LCP s'en trouve affecté.
La solution consiste à modifier le balisage de l'image:
<img src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Il s'agit du schéma optimal pour les images qui se trouvent dans la fenêtre d'affichage au démarrage, car le scanner de préchargement détecte et récupère la ressource image plus rapidement.
Dans cet exemple simplifié, il s'agit d'une amélioration de 100 millisecondes du LCP sur une connexion lente. Cette amélioration ne semble peut-être pas être une amélioration majeure, mais si vous considérez qu'il s'agit d'une solution rapide au balisage, la plupart des pages Web sont plus complexes que cet ensemble d'exemples. Cela signifie que les candidats au LCP peuvent être confrontés à de nombreuses autres ressources en termes de bande passante, c'est pourquoi ce type d'optimisation devient de plus en plus important.
Images de fond CSS
N'oubliez pas que l'outil d'analyse de préchargement du navigateur analyse le balisage. Il n'analyse pas les autres types de ressources, tels que les fichiers CSS, qui peuvent impliquer des récupérations d'images référencées par la propriété background-image
.
Tout comme le code HTML, les navigateurs traitent le code CSS dans leur propre modèle d'objet, appelé CSSOM. Si des ressources externes sont découvertes lors de la construction du CSSOM, elles sont demandées au moment de la découverte, et non par l'analyseur de préchargement.
Imaginons que le LCP candidat de votre page soit un élément avec une propriété CSS background-image
. Voici ce qui se passe lorsque les ressources se chargent:
Dans ce cas, l'outil d'analyse de préchargement n'est pas complètement vaincu. Malgré cela, si un LCP candidat sur la page provient d'une propriété CSS background-image
, vous allez précharger cette image:
<!-- Make sure this is in the <head> below any
stylesheets, so as not to block them from loading -->
<link rel="preload" as="image" href="lcp-image.jpg">
Cette indication rel=preload
est faible, mais elle permet au navigateur de découvrir l'image plus rapidement qu'elle ne le ferait autrement:
L'indice rel=preload
permet de détecter le LCP plus tôt, ce qui réduit la durée du LCP. Bien que cet indice permette de résoudre ce problème, la meilleure solution peut être d'évaluer si l'image candidate LCP doit être chargée depuis CSS. La balise <img>
vous permet de mieux contrôler le chargement d'une image adaptée à la fenêtre d'affichage, tout en permettant au scanner de préchargement de la découvrir.
Intégrer trop de ressources
L'intégration est une pratique qui place une ressource à l'intérieur du code HTML. Vous pouvez intégrer des feuilles de style dans des éléments <style>
, des scripts dans des éléments <script>
et pratiquement n'importe quelle autre ressource utilisant l'encodage base64.
L'intégration de ressources peut être plus rapide que leur téléchargement, car aucune demande distincte n'est envoyée pour la ressource. Il est intégré au document et se charge instantanément. Cependant, il présente des inconvénients importants:
- Si vous ne mettez pas en cache votre code HTML, mais que vous ne pouvez pas le faire si la réponse HTML est dynamique, les ressources intégrées ne sont jamais mises en cache. Cela affecte les performances, car les ressources intégrées ne sont pas réutilisables.
- Même si vous pouvez mettre en cache le code HTML, les ressources intégrées ne sont pas partagées entre les documents. Cela réduit l'efficacité de la mise en cache par rapport aux fichiers externes qui peuvent être mis en cache et réutilisés sur l'ensemble d'une origine.
- Si vous intégrez trop de ressources, le scanner de préchargement risque de ne pas détecter les ressources plus tard dans le document, car le téléchargement de ce contenu supplémentaire est plus long.
Prenons cette page comme exemple. Dans certaines conditions, le LCP candidat est l'image en haut de la page, et le CSS se trouve dans un fichier distinct chargé par un élément <link>
. La page utilise également quatre polices Web, demandées en tant que fichiers distincts de la ressource CSS.
Que se passe-t-il si le CSS et toutes les polices sont intégrés en tant que ressources base64 ?
L'intégration a des conséquences négatives sur le LCP dans cet exemple et sur les performances en général. La version de la page qui n'intègre rien affiche l'image LCP en 3,5 secondes environ. La page qui intègre tous les éléments n'affiche pas l'image LCP avant un peu plus de sept secondes.
Il n'y a pas que le scanner de préchargement en jeu. L'intégration de polices n'est pas une bonne stratégie, car le format base64 est inefficace pour les ressources binaires. Autre facteur à prendre en compte : les ressources de police externes ne sont pas téléchargées, sauf si le CSSOM détermine qu'elles sont nécessaires. Lorsque ces polices sont intégrées en base64, elles sont téléchargées, qu'elles soient nécessaires ou non à la page actuelle.
Un préchargement pourrait-il améliorer les choses ? Bien sûr. Vous pourriez précharger l'image LCP et réduire la durée du LCP, mais surcharger votre code HTML potentiellement impossible à mettre en cache à l'aide de ressources intégrées a d'autres conséquences négatives sur les performances. La métrique First Contentful Paint (FCP) est également affectée par ce schéma. Dans la version de la page où rien n'est intégré, le FCP est d'environ 2,7 secondes. Dans la version où tout est intégré, le FCP est d'environ 5,8 secondes.
Soyez très prudent lorsque vous intégrez des éléments dans HTML, en particulier les ressources encodées en base64. Ce n'est généralement pas recommandé, sauf pour les très petites ressources. Intégrer le moins possible d'éléments, car trop d'intégration joue sur le feu.
Balisage de rendu avec JavaScript côté client
Cela ne fait aucun doute: JavaScript affecte clairement la vitesse des pages. Non seulement les développeurs en dépendent pour assurer l'interactivité, mais ils ont également tendance à s'appuyer sur elle pour fournir le contenu lui-même. Cela permet d'améliorer l'expérience des développeurs d'une certaine manière, mais les avantages pour les développeurs ne se traduisent pas toujours par des avantages pour les utilisateurs.
Un modèle qui peut contourner l'outil d'analyse de préchargement consiste à afficher le balisage avec du code JavaScript côté client:
Lorsque des charges utiles de balisage sont contenues et affichées entièrement par JavaScript dans le navigateur, toutes les ressources de ce balisage sont invisibles pour l'outil d'analyse des préchargements. Cela retarde la découverte de ressources importantes, ce qui affecte certainement le LCP. Dans ces exemples, la requête d'image LCP est considérablement retardée par rapport à l'expérience équivalente affichée par le serveur, qui ne nécessite pas JavaScript pour s'afficher.
Cela s'éloigne un peu du sujet de cet article, mais les effets du balisage de rendu sur le client vont bien au-delà de la désactivation de l'outil d'analyse du préchargement. Tout d'abord, l'introduction de JavaScript pour alimenter une expérience qui n'en a pas besoin introduit un temps de traitement inutile qui peut affecter Interaction to Next Paint (INP).
De plus, l'affichage de très grandes quantités de balisage sur le client a plus de chances de générer des tâches longues que la quantité de balisage envoyée par le serveur. En plus du traitement supplémentaire qu'implique JavaScript, cela s'explique par le fait que les navigateurs diffusent le balisage du serveur et fragmentent l'affichage de manière à éviter les longues tâches. Le balisage affiché par le client, en revanche, est géré comme une tâche unique et monolithique, qui peut affecter des métriques de réactivité des pages telles que le temps total de blocage (TBT) ou le premier délai d'entrée (FID), en plus de l'INP.
La solution à ce cas de figure dépend de la réponse à la question suivante: Y a-t-il une raison pour laquelle le balisage de votre page ne peut pas être fourni par le serveur au lieu d'être affiché sur le client ? Si la réponse est "non", le rendu côté serveur ou le balisage généré de manière statique doivent être pris en compte dans la mesure du possible, car cela aidera l'outil d'analyse du préchargement à détecter et à extraire de manière opportuniste des ressources importantes à l'avance.
Si votre page a besoin de JavaScript pour associer des fonctionnalités à certaines parties du balisage de votre page, vous pouvez le faire avec SSR, soit avec du JavaScript vanille, soit avec l'hydratation pour tirer le meilleur parti des deux mondes.
Aidez l'outil d'analyse des préchargements
L'outil d'analyse des préchargements permet d'optimiser très efficacement les navigateurs afin d'accélérer le chargement des pages au démarrage. En évitant les tendances qui l'empêchent de détecter des ressources importantes à l'avance, vous ne vous contentez pas de simplifier le développement. Vous créez également de meilleures expériences utilisateur qui fourniront de meilleurs résultats pour de nombreuses métriques, y compris certaines statistiques Web Vitals.
Pour récapituler, voici ce que vous devez retenir de cet article:
- L'analyseur de préchargement du navigateur est un analyseur HTML secondaire qui analyse avant le principal s'il est bloqué pour découvrir de manière opportuniste les ressources qu'il peut récupérer plus tôt.
- L'outil d'analyse du préchargement ne peut pas détecter les ressources qui ne sont pas présentes dans le balisage fourni par le serveur lors de la requête de navigation initiale. Voici quelques exemples de méthodes permettant de contourner l'analyseur de préchargement :
- Injecter des ressources dans le DOM avec du code JavaScript, qu'il s'agisse de scripts, d'images, de feuilles de style ou de tout autre élément qui serait mieux adapté à la charge utile de balisage initial du serveur
- Chargement différé des images ou des iFrames dans la partie au-dessus de la ligne de flottaison à l'aide d'une solution JavaScript
- Affichage du balisage sur le client, qui peut contenir des références aux sous-ressources de document à l'aide de JavaScript.
- L'outil d'analyse des préchargements analyse uniquement le code HTML. Il n'examine pas le contenu d'autres ressources, en particulier les CSS, qui peuvent inclure des références à des éléments importants, y compris les candidats au LCP.
Si, pour une raison quelconque, vous ne pouvez pas éviter un schéma qui affecte négativement la capacité de l'outil d'analyse de préchargement à accélérer les performances de chargement, tenez compte de l'indice de ressource rel=preload
. Si vous utilisez rel=preload
, testez les outils de l'atelier pour vérifier qu'il donne l'effet escompté. Enfin, ne préchargez pas trop de ressources, car lorsque vous donnez la priorité à l'ensemble, rien ne se passe.
Ressources
- Les scripts asynchrones injectés à l'aide d'un script sont considérés comme dangereux
- Accélérer le chargement des pages grâce au pré-chargeur de navigateur
- Précharger les éléments critiques pour améliorer la vitesse de chargement
- Établir des connexions réseau suffisamment tôt pour améliorer la vitesse perçue des pages
- Optimiser Largest Contentful Paint
Image principale de Unsplash, de Mohammad Rahmani .