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.
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
getComputedStyleappels 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); }); }
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:
Les polices chargées par une page en turc. On passe de 6 fichiers et 2 origines externes à 3 fichiers sur une seule origine.
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}
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
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.
Décalage de mise en page sur les pages arabes (CLS)
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.