Découvrez ce qu'est l'outil d'analyse du préchargement du navigateur, comment il améliore les performances et comment l'éviter.
Un aspect négligé de l'optimisation de la vitesse de chargement des pages consiste à connaître un peu le fonctionnement interne des navigateurs. Les navigateurs effectuent certaines optimisations pour améliorer les performances d'une manière que nous, en tant que développeurs, ne pouvons pas faire, mais seulement tant que ces optimisations ne sont pas bloquées par inadvertance.
L'optimisation interne du navigateur à comprendre est le scanner de précharge du navigateur. Cet article explique le fonctionnement du scanner de préchargement et, surtout, comment éviter de l'entraver.
Qu'est-ce qu'un analyseur de préchargement ?
Chaque navigateur dispose d'un analyseur HTML principal qui tokenize le balisage brut et le transforme en modèle d'objet. Tout cela se passe sans problème 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, le rendu est bloqué pour éviter un flash de contenu sans style (FOUC), c'est-à-dire 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 le rendu 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 effectue son travail. C'est pourquoi il est courant de charger votre code JavaScript à la fin du document afin que les effets de l'analyse et de l'affichage bloqués soient minimes.
C'est pourquoi le navigateur doit bloquer à la fois l'analyse et l'affichage. Toutefois, il est déconseillé de bloquer l'une de ces étapes importantes, car elles peuvent retarder la découverte d'autres ressources importantes. Heureusement, les navigateurs font de leur mieux pour atténuer ces problèmes à l'aide d'un analyseur HTML secondaire appelé analyseur de préchargement.
Le rôle d'un analyseur 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.
Savoir quand l'outil d'analyse de préchargement 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. Pour déterminer si une page Web bénéficie du scanner de préchargement, il est essentiel de tenir compte de ces phénomènes de blocage. Pour ce faire, vous pouvez ajouter un délai artificiel aux requêtes afin de déterminer où le scanner de préchargement fonctionne.
Prenons l'exemple de cette page de texte et d'images de base avec une feuille de style. É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ù le scanner 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 des éléments de manière opportuniste pendant la période de blocage, et un plus grand nombre de requêtes de ressources seraient consécutives plutôt que simultanées.
Maintenant que cet exemple fictif est terminé, examinons quelques exemples concrets où le scanner de précharge peut être contourné, et ce que vous pouvez faire pour les corriger.
Scripts async
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'il s'exécutera dès que possible et qu'il ne bloquera pas le rendu. C'est l'idéal, n'est-ce pas ? Toutefois, si vous présumez que cet élément <script>
intégré vient après un élément <link>
qui charge un fichier CSS externe, vous obtiendrez un résultat non optimal:
Voyons ce qui s'est passé:
- À 0 seconde, le document principal est demandé.
- À 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é par le chargement de la feuille de style et que le code JavaScript intégré qui injecte le script
async
vient après cette feuille de style, à 2,6 secondes, la fonctionnalité fournie par le script n'est pas disponible dès que possible.
Ce n'est pas optimal, car la requête du script ne se produit qu'après le téléchargement de la feuille de style. Cela retarde l'exécution du script dès que possible. En revanche, comme l'élément <img>
est détectable dans le balisage fourni par le serveur, il est détecté par l'explorateur 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émonstration où le script async
est préchargé, la feuille de style est toujours chargée avec la priorité "Élevée", mais la priorité du script a été définie sur "Élevée".
Lorsque la priorité d'une ressource est augmentée, le navigateur lui alloue plus 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 entraîner un conflit 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 contournez pas le scanner de préchargement en l'injectant dans le DOM. Testez si nécessaire l'emplacement des éléments <script>
, ainsi que les 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 peut entraîner des problèmes potentiels de visibilité des ressources pour l'outil d'analyse de préchargement et retarder inutilement la découverte d'une référence à une image, son téléchargement, son décodage et sa présentation. 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 paresseux JavaScript. Lorsque l'image est défilée dans le viewport, le chargeur paresseux supprime le préfixe data-
. Par conséquent, 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 pas de problème tant qu'il n'est pas appliqué aux images qui se trouvent dans le viewport 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 paresseux.
Selon 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 Largest Contentful Paint (LCP). Lorsque le scanner de préchargement ne peut pas récupérer de manière spéculative la ressource d'image à l'avance(peut-être au moment où les feuilles de style de la page bloquent le rendu), le LCP en souffre.
La solution consiste à modifier la balise 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 le viewport au démarrage, car le scanner de préchargement détecte et récupère 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 peut ne pas sembler être une amélioration majeure, mais c'est le cas si vous considérez que la solution consiste à corriger rapidement la balise 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 le scanner de préchargement du navigateur analyse la mise en forme. Il n'analyse pas d'autres types de ressources, comme les CSS, qui peuvent impliquer des récupérations 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'objets, appelé CSSOM. Si des ressources externes sont détectées lors de la création du CSSOM, elles sont demandées au moment de la détection, et non par l'explorateur de préchargement.
Imaginons que le candidat au LCP de votre page soit un élément avec une propriété CSS background-image
. Voici ce qui se passe lorsque les ressources sont chargées:
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 information rel=preload
est petite, mais elle aide le navigateur à découvrir l'image plus tôt qu'il ne le ferait autrement:
Avec l'indice rel=preload
, le candidat au LCP est découvert plus tôt, ce qui réduit le temps de 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 une balise <img>
, vous pouvez mieux contrôler le chargement d'une image adaptée au viewport, tout en permettant au scanner de préchargement de la découvrir.
Insérer trop de ressources
L'intégration consiste à placer une ressource dans le 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 à l'aide de l'encodage Base64.
L'intégration de ressources peut être plus rapide que leur téléchargement, car aucune requête distincte n'est émise pour la ressource. Il se trouve dans le document et se charge instantanément. Cependant, cette approche présente des inconvénients importants:
- Si vous ne mettez pas en cache votre code HTML (et 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 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 dans l'ensemble d'une origine.
- Si vous insérez trop de contenu intégré, le scanner de préchargement est retardé dans la découverte des ressources plus loin dans le document, car le téléchargement de ce contenu supplémentaire intégré prend plus de temps.
Prenons cette page comme exemple. Dans certaines conditions, le candidat au LCP 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 en tant que fichiers distincts de la ressource CSS.
Que se passe-t-il si le CSS et toutes les polices sont intégrées en tant que ressources base64 ?
Dans cet exemple, l'impact de l'intégration a des conséquences négatives sur le LCP et 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 inclut tout en ligne n'affiche l'image LCP qu'au bout de plus de sept secondes.
Il ne s'agit pas seulement du scanner de préchargement. L'intégration des polices n'est pas une bonne stratégie, car le format base64 est inefficace pour les ressources binaires. Un autre facteur est que les ressources de polices externes ne sont pas téléchargées, sauf si elles sont jugées nécessaires par le CSSOM. 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.
Un préchargement pourrait-il améliorer les choses ? Bien sûr. Vous pouvez précharger l'image du LCP et réduire le délai de LCP, mais gonfler votre code HTML potentiellement non en cache avec des ressources intégrées a d'autres conséquences négatives sur les performances. Ce modèle affecte également le First Contentful Paint (FCP). 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.
Faites très attention à insérer des éléments dans le code HTML, en particulier des ressources encodées en base64. En règle générale, cette pratique n'est pas recommandée, sauf pour les ressources très petites. Insérez le code inline le moins possible, car trop d'éléments intégrés peut s'avérer dangereux.
Rendre le balisage avec JavaScript côté client
Il n'y a pas de doute: JavaScript a un impact sur la vitesse des pages. Les développeurs s'appuient sur elle pour fournir de l'interactivité, mais aussi pour diffuser du contenu. 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 le scanner de préchargement consiste à afficher du balisage avec JavaScript côté client:
Lorsque les charges utiles de balisage sont contenues dans JavaScript et entièrement affichées par celui-ci dans le navigateur, toutes les ressources de ce balisage sont effectivement invisibles pour le scanner de préchargement. Cela retarde la découverte de ressources importantes, ce qui a certainement un impact sur le LCP. Dans ces exemples, la requête de l'image LCP est considérablement retardée par rapport à l'expérience équivalente rendue par le serveur qui n'a pas besoin de JavaScript pour s'afficher.
Cela s'écarte un peu de l'objet de cet article, mais les effets du rendu du balisage sur le client vont bien au-delà de la désactivation du scanner de préchargement. Par exemple, l'introduction de JavaScript pour alimenter une expérience qui n'en a pas besoin entraîne un temps de traitement inutile qui peut affecter l'interaction jusqu'à la prochaine peinture (INP). L'affichage d'une quantité extrêmement importante de balisage sur le client est plus susceptible de générer des tâches longues par rapport à la même quantité de balisage envoyée par le serveur. En dehors du traitement supplémentaire que JavaScript implique, les navigateurs transmettent le balisage du serveur en streaming et segmentent le rendu de manière à limiter les tâches longues. En revanche, le balisage côté client est géré comme une tâche monolithique unique, 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 envisagés dans la mesure du possible, car ils aideront le scanner de préchargement à découvrir et à récupérer de manière opportuniste des ressources importantes à l'avance.
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 le préchargeur à vous aider
L'analyseur de préchargement est une optimisation de navigateur très efficace qui permet de charger les pages plus rapidement au démarrage. En évitant les modèles qui empêchent le navigateur de découvrir à l'avance des ressources importantes, vous ne simplifiez pas seulement le développement, mais vous créez également une meilleure expérience utilisateur qui génère de meilleurs résultats pour de nombreuses métriques, y compris certaines métriques Web Vitals.
Pour résumer, voici les points clés à retenir de cet article:
- Le scanner de préchargement du navigateur est un analyseur HTML secondaire qui analyse avant l'analyseur principal s'il est bloqué afin de découvrir de manière opportuniste les ressources qu'il peut récupérer 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 permettant de contourner le scanner de préchargement (liste non exhaustive) :
- 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 placé dans la charge utile de balisage initiale du serveur.
- Chargement paresseux des images ou des 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. Elle n'examine pas le contenu d'autres ressources, en particulier les fichiers 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é du scanner de préchargement à accélérer les performances de chargement, envisagez l'indice de ressource rel=preload
. Si vous utilisez rel=preload
, testez-le dans des outils de laboratoire pour vous assurer qu'il produit l'effet souhaité. Enfin, ne préchargez pas trop de ressources, car si vous priorisez tout, rien ne sera prioritaire.
Ressources
- Les scripts asynchrones injectés à l'aide d'un script sont considérés comme dangereux
- Comment le préchargeur du navigateur accélère le chargement des pages
- Précharger les composants critiques pour améliorer la vitesse de chargement
- Établir des connexions réseau à l'avance pour améliorer la vitesse de chargement perçue
- Optimiser le Largest Contentful Paint
Image principale issue de Unsplash, par Mohammad Rahmani .