Découvrez ce qu'est l'outil d'analyse de préchargement du navigateur, comment il améliore les performances et comment vous protéger.
Un aspect négligé de l'optimisation de la vitesse des pages consiste à bien connaître les composants internes des navigateurs. Les navigateurs effectuent certaines optimisations afin d'améliorer les performances, ce qui est impossible pour les développeurs, mais uniquement à condition que ces optimisations ne soient pas contrées involontairement.
L'outil d'analyse de préchargement des navigateurs est une optimisation interne à comprendre. Cet article décrit le fonctionnement de l'analyseur de préchargement et, plus important encore, explique comment éviter les obstacles.
Qu'est-ce qu'un analyseur de précharge ?
Chaque navigateur dispose d'un analyseur HTML principal qui tokenise le balisage brut et le traite en un modèle d'objet. Tout cela se poursuit jusqu'à ce que l'analyseur se met en pause 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'affichage est bloqué afin d'éviter que du contenu non stylisé ne s'affiche sous un style, c'est-à-dire lorsqu'une version sans style d'une page s'affiche brièvement avant que des styles ne 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 pendant que l'analyseur HTML principal sera toujours en train de travailler. C'est pourquoi il est courant de charger votre code JavaScript à la fin du document, afin que les effets du blocage de l'analyse et de l'affichage s'aggravent.
C'est pour ces raisons que le navigateur devrait bloquer à la fois l'analyse et l'affichage. Pourtant, il n'est pas souhaitable de bloquer l'une ou l'autre de ces étapes importantes, car elles peuvent retarder la découverte d'autres ressources importantes. Heureusement, les navigateurs s'efforcent de limiter ces problèmes à l'aide d'un analyseur HTML secondaire, appelé outil d'analyse du préchargement.
Le rôle d'un outil d'analyse de préchargement est spéculatif, c'est-à-dire qu'il examine le balisage brut afin de trouver des ressources à extraire de manière opportuniste avant que l'analyseur HTML principal ne les découvre autrement.
Déterminer si l'analyseur de précharge fonctionne
L'outil d'analyse de préchargement existe parce que le rendu et l'analyse sont bloqués. Si ces deux problèmes de performances n'avaient jamais existé, l'outil d'analyse du préchargement ne serait pas très utile. La clé pour déterminer si une page Web bénéficie de l'analyseur de préchargement dépend de ces phénomènes bloquants. Pour ce faire, vous pouvez introduire un délai artificiel pour les requêtes afin de savoir où fonctionne le scanner de préchargement.
Prenons l'exemple de cette page contenant du texte et des images de base, ainsi qu'une feuille de style. Étant donné que les fichiers CSS bloquent 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ù l'analyseur de préchargement fonctionne.
Comme vous pouvez le voir dans la cascade, le scanner de préchargement détecte l'élément <img>
même lorsque 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.
Une fois cet exemple de jouet terminé, examinons des schémas concrets où le scanner de précharge peut être contourné, et ce qui peut être fait pour les corriger.
async
scripts injectés
Imaginons que votre <head>
contienne du code HTML incluant du code JavaScript intégré, comme suit :
<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 était appliqué. Cela signifie qu'elle s'exécutera dès que possible et qu'elle ne bloquera pas l'affichage. Cela semble optimal, n'est-ce pas ? Pourtant, si vous présumez que cet élément <script>
intégré est placé après un élément <link>
qui charge un fichier CSS externe, vous obtiendrez un résultat non optimal:
Analysons ce qui s'est passé ici:
- À 0 seconde, le document principal est demandé.
- Au bout de 1,4 seconde, le premier octet de la requête de navigation arrive.
- À 2,0 secondes, le 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
arrive 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 n'est pas optimal, car la demande de script n'est envoyée 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 l'est par l'analyseur 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>
Vous obtenez le résultat suivant :
Vous pourriez être tenté de suggérer que ces problèmes peuvent être résolus à l'aide de rel=preload
. Cela fonctionnerait certainement, mais cela pourrait 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 il en crée un autre : le script async
des deux premières démonstrations, bien qu'il soit chargé dans le <head>
, est chargé avec une priorité "Faible", tandis que la feuille de style est chargée avec une priorité "Élevée". Dans la dernière démo où le script async
est préchargé, la feuille de style est toujours chargée au niveau le plus élevé. mais la priorité du script est passée à "High" (É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 de bande passante. Cela peut être un facteur dans les cas de connexions lentes ou lorsque les ressources sont très volumineuses.
La réponse est simple: si un script est nécessaire au démarrage, ne déjouez pas l'outil d'analyse de préchargement en l'injectant dans le DOM. Si nécessaire, testez différents emplacements d'éléments <script>
, ainsi que des attributs tels que defer
et async
.
Chargement différé avec JavaScript
Le chargement paresseux est une excellente méthode de conservation des données, souvent appliquée aux images. Cependant, le chargement paresseux est parfois appliqué de manière incorrecte aux images qui se trouvent "au-dessus de la ligne de flottaison", pour ainsi dire.
Cela entraîne des problèmes potentiels de visibilité des ressources lorsque l'outil d'analyse de préchargement est concerné, 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 cet exemple de 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-
, ce qui signifie que dans l'exemple précédent, data-src
devient src
. Cette mise à jour invite le navigateur à récupérer la ressource.
Ce modèle ne pose aucun 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 le lecteur 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 de l'image n'est pas découverte plus tôt. Pire encore, le chargement de l'image est retardé jusqu'à 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 un élément candidat pour le format Largest Contentful Paint (LCP). Lorsque l'outil d'analyse du préchargement ne parvient pas à récupérer de manière spéculative la ressource image à l'avance, éventuellement au moment où la ou les feuilles de style de la page bloquent l'affichage, le LCP en souffre.
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 modèle optimal pour les images qui se trouvent dans la fenêtre d'affichage au démarrage, car l'outil d'analyse du préchargement détecte et extrait la ressource d'image plus rapidement.
Dans cet exemple simplifié, le résultat est une amélioration de 100 millisecondes du LCP sur une connexion lente. Cela ne semble peut-être pas être une amélioration considérable, mais c'est lorsque vous considérez que la solution est une correction rapide du balisage et que la plupart des pages Web sont plus complexes que cet ensemble d'exemples. Cela signifie que les candidats au LCP peuvent devoir se battre pour la bande passante avec de nombreuses autres ressources. Par conséquent, des optimisations comme celle-ci deviennent de plus en plus importantes.
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 CSS, qui peut impliquer l'extraction d'images référencées par la propriété background-image
.
Comme pour 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.
Supposons que le LCP candidat de votre page soit un élément ayant une propriété CSS background-image
. Voici ce qui se passe lorsque les ressources se chargent:
Dans ce cas, le scanner de préchargement n'est pas tant contourné que non impliqué. Toutefois, si un candidat au LCP sur la page provient d'une propriété CSS background-image
, vous devez 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 mineure, mais elle permet au navigateur de découvrir l'image plus tôt qu'il ne le ferait autrement:
Avec l'indice rel=preload
, le candidat LCP est découvert plus tôt, ce qui réduit la durée du LCP. Bien que cette indication aide à résoudre ce problème, il est peut-être préférable d'évaluer si votre candidat LCP d'image doit être chargé à partir du CSS. Avec un tag <img>
, vous aurez davantage de contrôle sur le chargement d'une image adaptée à la fenêtre d'affichage, tout en permettant à l'analyseur de préchargement de la découvrir.
Intégrer trop de ressources
L'intégration est une pratique qui place une ressource dans le code HTML. Vous pouvez intégrer des feuilles de style dans les éléments <style>
, des scripts dans des éléments <script>
et pratiquement toute autre ressource utilisant l'encodage base64.
Il peut être plus rapide d'intégrer des ressources que de les télécharger, car aucune demande distincte n'est envoyée pour ces ressources. Il se trouve dans le document et se charge instantanément. Cependant, il existe des inconvénients majeurs:
- Si vous ne mettez pas en cache votre code HTML, et que ce n'est pas possible 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 du 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 d'éléments intégrés, vous retardez l'analyse de préchargement pour qu'ils ne découvrent pas les ressources ultérieurement dans le document, car le téléchargement de ce contenu supplémentaire intégré prend plus de temps.
Prenez 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, qui sont demandées sous la forme de 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 en base64 ?
L'impact de l'intégration a des conséquences négatives sur le LCP de cet exemple, ainsi que sur les performances en général. La version de la page qui n'intègre rien affiche l'image LCP en environ 3,5 secondes. La page qui intègre tout ne peigne l'image LCP que pendant un peu plus de sept secondes.
Il y a plus en jeu ici que le simple scanner de préchargement. L'intégration des polices n'est pas une bonne stratégie, car le format base64 n'est pas efficace pour les ressources binaires. Autre facteur : les ressources de police externes ne sont téléchargées que si le CSSOM détermine qu'elles sont nécessaires. Lorsque ces polices sont intégrées au format Base64, elles sont téléchargées, que ce soit nécessaire pour la page actuelle ou non.
Le préchargement pourrait-il améliorer les choses ici ? Bien sûr. Vous pourriez précharger l'image LCP et réduire la durée du LCP, mais la surcharge de votre code HTML potentiellement ne pouvant pas être mis en cache avec des ressources intégrées aura d'autres conséquences négatives sur les performances. First Contentful Paint (FCP) est également affecté par ce schéma. Dans la version de la page où rien n'est intégré, la valeur 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 le code HTML, en particulier les ressources encodées en base64. En général, ce n'est pas recommandé, sauf pour les très petites ressources. Intégrez le moins d'éléments possible, car trop l'intégration peut jouer avec le feu.
Balisage de rendu avec JavaScript côté client
Cela ne fait aucun doute: JavaScript a un impact certain sur la vitesse des pages. Les développeurs en dépendent non seulement pour assurer l'interactivité, mais ils ont aussi tendance à s'y fier pour diffuser des contenus eux-mêmes. Cela permet d'améliorer l'expérience des développeurs à certains égards : mais les avantages pour les développeurs ne se traduisent pas toujours en avantages pour les utilisateurs.
Un modèle qui peut contourner le scanner de préchargement consiste à afficher du balisage avec JavaScript côté client :
Lorsque les charges utiles de balisage sont contenues et affichées entièrement par JavaScript dans le navigateur, toutes les ressources de ce balisage sont effectivement invisibles pour l'analyseur de préchargement. Cela retarde la découverte de ressources importantes, ce qui affecte certainement le LCP. Dans ces exemples, la demande d'image LCP est considérablement retardée par rapport à l'expérience équivalente affichée sur le serveur qui ne nécessite pas l'affichage de JavaScript.
Cela s'éloigne un peu du sujet de cet article, mais les effets du balisage du rendu sur le client vont bien au-delà de la contournement de l'analyseur de préchargement. D'une part, l'introduction de JavaScript pour une expérience qui n'en a pas besoin introduit un temps de traitement inutile qui peut affecter l'interaction avec Next Paint (INP). L'affichage de très grandes quantités de balisage sur le client a plus de chances de générer de longues tâches que le même volume de balisage envoyé par le serveur. En plus du traitement supplémentaire qu'implique JavaScript, cela s'explique par le fait que les navigateurs diffusent le balisage depuis le serveur et fragmentent le rendu de manière à limiter les longues tâches. Le balisage rendu par le client, en revanche, est géré comme une seule tâche monolithique, ce qui peut affecter l'INP d'une page.
La solution à ce problème dépend de la réponse à cette question : Existe-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 (SSR) 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 de précharge à détecter et extraire à l'avance des ressources importantes de manière opportuniste.
Si votre page a besoin de JavaScript pour associer des fonctionnalités à certaines parties de son balisage, vous pouvez toujours le faire avec le SSR, soit avec du JavaScript standard, soit avec l'hydratation pour profiter des avantages des deux approches.
Aidez l'outil d'analyse de préchargement à vous aider
L'analyseur de préchargement est une optimisation de navigateur très efficace qui permet aux pages de se charger plus rapidement au démarrage. En évitant les modèles qui l'empêchent de découvrir des ressources importantes à l'avance, vous ne vous simplifiez pas seulement le développement, mais vous créez également de meilleures expériences utilisateur qui permettront d'obtenir de meilleurs résultats pour de nombreuses métriques, y compris certaines statistiques Web Vitals.
Pour récapituler, voici les points suivants que vous devez retenir de ce post:
- L'analyseur de préchargement du navigateur est un analyseur HTML secondaire qui analyse avant le principal s'il est bloqué afin de découvrir avec opportunisme les ressources qu'il peut extraire plus tôt.
- Les ressources qui ne figurent pas dans le balisage fourni par le serveur lors de la requête de navigation initiale ne peuvent pas être détectées par l'explorateur de préchargement. Voici quelques exemples de méthodes pour contourner le scanner de précharge :
- Injecter des ressources dans le DOM avec 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 initiale du serveur.
- Chargement différé d'images ou d'iFrames au-dessus de la ligne de flottaison à l'aide d'une solution JavaScript.
- Rendu du balisage sur le client, qui peut contenir des références aux sous-ressources de document à l'aide de JavaScript.
- Le scanner de préchargement n'analyse que le code HTML. Il n'examine pas le contenu d'autres ressources, en particulier les ressources CSS, qui peuvent inclure des références à des éléments importants, y compris des 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écharge à 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 vous assurer que vous obtenez l'effet souhaité. Enfin, ne préchargez pas trop de ressources, car lorsque vous hiérarchisez tout, rien ne l'est.
Ressources
- Scripts asynchrones injectés à l'aide d'un script considéré comme dangereux
- Comment le pré-chargeur de navigateur accélère-t-il le chargement des pages ?
- Précharger les éléments critiques pour améliorer la vitesse de chargement
- Établissez des connexions réseau rapidement pour améliorer la vitesse perçue des pages
- Optimizing Largest Contentful Paint
Image héros tirée de Unsplash, par Mohammad Rahmani .