Carnet · Note 001 · Performances web

Où se cachaient les 18 derniers points d’un site statique

Images compressées, JavaScript limité à trois fichiers, aucun framework. Lighthouse affichait quand même 82. Les derniers points se trouvaient à trois endroits que l’outil nommait sans les expliquer.

Serhat Belen

Fondateur, BLN Global · · 7 min de lecture

Réponse courte

Les images et JavaScript étaient déjà au point. Les derniers points se jouaient à trois endroits. D’abord, le contrôle censé éviter un travail inutile lisait lui-même la mise en page et forçait le navigateur à recalculer. Ensuite, la feuille de style et les polices externes retardaient le premier affichage de 600 ms. On a intégré le CSS à la page et hébergé les polices chez nous. Enfin, 6 des 24 alertes de contraste étaient fausses. L’analyse complète a ensuite révélé 13 pages absentes de l’échantillon.

Ce site n’utilise aucun framework. Des scripts Python génèrent les pages en HTML brut. Un seul fichier CSS gère le style et trois petits fichiers contiennent le JavaScript. Les images sont produites en WebP et en trois tailles. Bref, tout ce qui semblait évident pour accélérer le site était déjà fait.

Le score mobile restait à 82.

Après les corrections évidentes, les derniers points ne se cachaient pas dans une grosse erreur. Ils étaient répartis à trois endroits. Pour les trouver, il fallait aller au-delà de l’alerte affichée par l’outil et comprendre ce qu’elle ce que tu veux dire il a fallu comprendre.

Première cachette : le contrôle écrit pour éviter le travail

L’alerte « recalcul forcé » (forced reflow) de Lighthouse indique que JavaScript oblige le navigateur à calculer la mise en page trop tôt. Le navigateur regroupe les changements de style avant de les appliquer. Si un script demande une mesure entre-temps, il doit tout recalculer sur-le-champ.

L’alerte pointait la ligne source, mais rien de visible ne s’y mesurait. Trouvé en trois passes.

  • Au premier tour getComputedStyle appels retirés. Le score n’a pas bougé.
  • Au deuxième passage, le vrai coupable est apparu : un bout de code écrit pour sauter le travail inutile if (window.scrollY > 0) vérification. Cette vérification lit elle-même la mise en page. La ligne censée éviter ce travail finissait par le déclencher.
  • Au troisième passage, la fonction a disparu. CSS définissait déjà l'état initial. JavaScript n'avait aucune raison de le recréer.

Il reste aussi un motif void element.offsetWidth C’était une bidouille connue: forcer exprès la mise en page pour relancer une transition. À la place, deux requestAnimationFrame on l’a ajouté. Il fait le même travail sans forcer la mise en page.

function yenidenBaslat(fn) { /* Si l’onglet est en arrière-plan, rAF ne se lance pas et la démo reste vide. */ if (document.hidden) { fn(); return; } requestAnimationFrame(function () { requestAnimationFrame(fn); }); }
Relancer la transition sans forcer la mise en page. Prévois une issue pour les onglets en arrière-plan. rAF ne s'y déclenche jamais.

La leçon : une alerte de performance ne dit pas toujours « tu en fais trop ». Elle dit souvent « tu le fais au mauvais moment ».

Deuxième cachette : les 600 ms avant le premier affichage

La deuxième alerte concernait les ressources qui bloquaient le premier affichage. À l’ouverture, le navigateur ne dessinait rien avant d’avoir téléchargé et traité la feuille de style, puis les polices. Délai mesuré : 600 millisecondes.

On a mis la feuille de style dans la page

Un fichier CSS séparé arrive du cache à la deuxième visite. C’est un plus. Mais à la première, il ajoute un aller-retour, et c’est celle que vit la personne venue depuis un résultat de recherche. À la publication, on minifie le CSS et on l’intègre dans la page. On a mesuré: après compression Brotli, la version intégrée pesait moins que le fichier séparé + la deuxième requête.

On héberge les polices chez nous

L’installation de Google Fonts appelait deux origines : une pour le style fonts.googleapis.com, pour les fichiers fonts.gstatic.com. Pour chaque nouvelle origine, le navigateur refait une résolution DNS, une poignée de main TLS et ouvre une connexion. Quand tu l’enlèves, on mesure ça:

250 KB165 KB

Les polices chargées par une page en turc. On passe de 6 fichiers et 2 origines externes à 3 fichiers sur une seule origine.

Comment on a mesuré Le CSS de Google Fonts a été chargé avec une vraie identité Chrome, unicode-range À partir des champs, on a isolé les sous-ensembles que la page turque téléchargerait vraiment (latin + latin-ext), puis pesé les fichiers un par un. En face, ceux de ce dépôt assets/fonts taille réelle des fichiers.

Une autre note raconte comment les polices ont maigri et ce que le passage à dix langues a coûté : dix langues, trois fichiers de police.

Troisième cas : là où l’outil se trompe

L'audit d'accessibilité a signalé 24 problèmes de contraste. Les corriger tous sans vérifier aurait cassé la moitié du design. 6 alertes étaient fausses.

Raison content-visibility:auto était. Cette fonction accélère le premier affichage en retardant le rendu des zones hors écran. Exactement ce qu'on voulait. Mais leur couleur calculée n'arrivait pas jusqu'à l'outil d'audit, qui signalait du « blanc sur blanc ». Rien de tel à l'écran.

On a mesuré chaque contraste dans le navigateur. 18 alertes étaient justes et ont été corrigées. Les 6 autres venaient d'un angle mort de l'outil, content-visibility on ne l’a retiré que des deux sections en cause. Ailleurs, on l’a gardé.

Une alerte d’un outil d’audit ne prouve pas qu’il y a une erreur à corriger. C’est une hypothèse. Tu vérifies, puis tu corriges. Cette leçon revient deux fois sur ce site. La première, dans la note sur le contrôle de langue on raconte.

Et la plaie qu’on s’est faite nous-mêmes

Pour éviter les décalages, ajoute à chaque image width et height On a ajouté l’attribut. Bon choix: le navigateur réserve la place de l’image avant de la télécharger. Mais les blocs de démo ont cassé: certaines images se sont affichées en 58 pixels de large et 1500 pixels de haut.

La raison : dans le HTML, width et height les attributs guident l’affichage, et dans le CSS height ils renseignent la valeur. Dans la maquette, ces cases n’ont que aspect-ratio était défini, height était vide. L’attribut s’y est glissé et a écrasé le ratio. La correction tient en une ligne:

img{max-width:100%;height:auto;display:block}
Les attributs restent en place et bloquent toujours le décalage de mise en page. Le CSS reprend la hauteur.

Et voilà où l’exploration sert vraiment

Quand l’accueil a affiché 100, on a cru que c’était fini. Faux. On mesurait un échantillon. Le site ses 150 pages au complet passées une par une dans Lighthouse. Résultat : 137 pages à 100 dans les quatre rubriques, pas 13 pages. Accessibilité, bonnes pratiques et SEO affichaient 100 sur chaque page, seule la performance variait.

Sur treize pages, onze étaient dans la même langue : l’arabe. La plus basse : 83.

/ar/safecoast/ 83 /ar/laya/ 84 /ar/localmind/ 84 /ar/adforge/ 85 /ar/garanti/ 85 /ar/gtin/ 86 /ar/visionguard/ 86 /ar/ 89 /ar/growthos/ 97 /ar/dream/ 99 /ar/twin/ 99
Les pages en arabe repérées par l'analyse complète. Les deux écarts restants, /pl/modelmesh/ et /ru/terms.html, tous deux à 98, restent dans la marge normale entre deux mesures.

Lighthouse a donné la raison : Police web chargée → ar.woff2. La police arabe arrivait trop tard et recomposait la page. La police arabe de secours avait des mesures très éloignées de Noto Sans Arabic, donc le décalage montait haut. Le correctif reprend la décision déjà prise pour Martian Mono : swap non optional.

0,2870

Décalage de mise en page sur les pages arabes (CLS)

Comment on a mesuré Après le changement, nous avons remesuré les 15 pages arabes : CLS à zéro sur les 15. Les scores de performance sont passés de 83-89 à 98-100, et 9 pages sur 15 atteignent pile 100. Mesure prise sur le site en ligne avec Lighthouse.

La vraie leçon ne concerne pas les performances. Tester un échantillon puis annoncer « site 100 », c'est prétendre connaître des pages que tu n'as pas mesurées. Quinze pages sont ainsi restées à 83 pendant des mois. On ne les avait jamais regardées.

Limite du résultat

Le score reste une mesure de labo. Il suppose un appareil et un réseau fixes. Le téléphone et la connexion de ton visiteur peuvent faire moins bien. Deux tests de suite sur la même page donnent entre 98 et 100. Un score de 100 ne veut pas dire « rapide pour tout le monde ». Il indique que les obstacles mesurables ont été supprimés. Quand les données réelles s’accumuleront, il faudra regarder les données terrain de Chrome.

Ces trois correctifs ont un point commun: aucun ne demandait de travailler plus. Il fallait retirer du travail fait au mauvais moment, ou du travail à ne pas faire du tout.

FAQ

Que veut dire Lighthouse par « redistribution forcée » ?

JavaScript force le navigateur à calculer trop tôt les changements de style en attente. D’habitude, il les regroupe et les applique d’un coup. Si un script lit une mesure entre-temps, le navigateur doit tout traiter immédiatement. offsetWidth, getBoundingClientRect, scrollY et getComputedStyle toutes ces lectures le déclenchent.

Faut-il toujours intégrer le CSS dans la page ?

Non. Un fichier séparé arrive du cache à la deuxième visite. C’est un plus. L’intégrer accélère la première visite et ralentit les suivantes. Il faut décider selon le site: si la plupart des gens arrivent depuis un résultat de recherche, l’intégration gagne. Chez nous, on l’a mesuré. Ça gagnait.

Je dois arrêter content-visibility ?

Non. En retardant le rendu des sections hors écran, cette option accélère bien le premier affichage. Le problème vient de l’outil d’audit, incapable de lire la couleur d’une section différée. Retire-la seulement des sections mal signalées.

Un score de 100 veut-il vraiment dire que le site est rapide ?

Non. C’est une mesure en laboratoire qui suppose un appareil et un réseau fixes. Testée plusieurs fois de suite, la même page oscille entre 98 et 100. Le résultat réel apparaît dans les données de terrain de Chrome, qui mettent du temps à se remplir.

Pourquoi mesurer toutes les pages au lieu d’un échantillon ?

Chez nous, 13 pages sur 150 ne sont jamais sorties dans l’échantillon, et 11 étaient dans la même langue. La page d’accueil était à 100, les pages arabes restaient à 83. Tu ne peux rien affirmer sur une page que tu n’as pas mesurée.

Sources

Projets liés