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 procèdent à certaines optimisations afin d'améliorer les performances de façon inédite, en tant que 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. Le processus se poursuit jusqu'à ce que l'analyseur soit mis 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'analyse et l'affichage sont bloqués afin d'éviter la flash de contenu sans style (FOUC), lorsqu'une version sans style d'une page peut être vue 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 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, ce qui signifie 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
Cet outil existe en raison du blocage de l'affichage et de l'analyse. Si ces deux problèmes de performances n'ont jamais existé, l'analyseur de 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, l'outil d'analyse de préchargement détecte l'élément <img>
même lorsque l'affichage et l'analyse de documents 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 fichier <head>
contienne du code JavaScript intégré, comme celui-ci:
<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. Ainsi, lorsque ce script est injecté, il se comporte comme si l'attribut async
lui avait été 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.
- Au bout de 2 secondes, le code 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.
Alors, que se passe-t-il si vous utilisez une balise <script>
standard avec l'attribut async
plutôt que d'injecter le script dans le DOM ?
<script src="/yall.min.js" async></script>
Voici le résultat:
Vous serez peut-ê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 ?
Préchargement des "correctifs" Le problème est ici, mais il introduit un nouveau problème: le script async
des deux premières démonstrations (bien qu'il soit chargé dans <head>
) est chargé à l'état "Faible". alors que la feuille de style est chargée avec le niveau de priorité "Le plus élevé" leur priorité. 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 en cas de connexion lente ou de ressources importantes.
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 différé est une excellente méthode de conservation des données, souvent appliquée aux images. Toutefois, il arrive que le chargement différé soit appliqué à tort aux images situées "au-dessus de la ligne de flottaison".
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 l'exemple du balisage d'image suivant:
<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 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'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 LCP (Largest Contentful Paint). 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 récupère plus rapidement la ressource image.
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 quand 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 être confrontés à une quantité de bande passante avec de nombreuses autres ressources. Par conséquent, les optimisations de ce type 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 les CSS, qui peuvent 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écharge n'est pas
tellement contrôlé qu'il n'est pas impliqué. Même dans ce cas, si un candidat LCP sur la page provient d'une propriété CSS background-image
, vous allez vouloir 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 légère, 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 cet indice permette de résoudre ce problème, il est préférable d'évaluer si votre image candidate LCP doit être chargée à 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 le chargement des ressources par la suite 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'est pas intégré affiche l'image LCP en 3,5 secondes environ. 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 en base64, elles sont téléchargées, qu'elles soient nécessaires ou non pour la page actuelle.
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é, 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 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 offrir de 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 l'analyseur de préchargement consiste à afficher le 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 alimenter une expérience qui n'en a pas besoin introduit un temps de traitement inutile qui peut affecter INP (Interaction to Next Paint). 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 à partir du 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 scénario 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 (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 code JavaScript pour associer une fonctionnalité à certaines parties de son balisage, vous pouvez toujours le faire avec le format SSR, soit avec la version JavaScript vanilla, soit avec l'hydratation pour tirer le meilleur parti des deux mondes.
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 offrent 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.
- L'outil d'analyse du préchargement ne peut pas détecter les ressources non présentes dans le balisage fourni par le serveur lors de la requête de navigation initiale. 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.
- Balisage de rendu sur le client pouvant contenir des références à des sous-ressources de documents à l'aide de JavaScript.
- L'outil d'analyse 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 du navigateur accélère 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 .