Il y a un plaisir coupable que tout développeur front connaît : lancer un audit Lighthouse, voir les jauges monter une à une, et obtenir le feu d’artifice — quatre scores verts, confettis à l’écran, 100/100 partout. On sait que c’est un outil de mesure synthétique, pas une fin en soi. Mais avouons-le : c’est profondément satisfaisant. J’ai dû lancer cet audit une vingtaine de fois le soir où on a mis en prod le nouveau site, juste pour revoir les confettis.
Ce petit moment de dopamine, c’est ce qui m’a poussé, il y a quelques mois, à passer plusieurs jours à optimiser notre nouveau site. Pas quelques heures un vendredi après-midi — plusieurs jours, à traquer des dizaines de millisecondes, à déplacer un preload d’une ligne, à mesurer l’impact d’une font en moins.
Et en chemin, je me suis posé la question que tout le monde me pose quand j’en parle : en 2026, avec la fibre partout et des navigateurs qui optimisent tout seuls, est-ce que ça sert encore à quelque chose ?
”Avec la 5G et les navigateurs modernes, on s’en fiche, non ?”
C’est l’argument qu’on entend le plus souvent — y compris chez des développeurs expérimentés. Les connexions sont rapides, les appareils sont puissants, Chrome fait du prefetch intelligent. Pourquoi se fatiguer ?
Je pensais un peu la même chose avant de plonger dans les métriques réelles du terrain. Sauf que la réalité est plus nuancée que ce qu’on vit depuis nos MacBook Pro branchés en fibre.
Le réseau n’est pas le goulot d’étranglement principal
La bande passante a explosé, c’est vrai. Mais la latence, elle, n’a pas suivi au même rythme. Un aller-retour serveur prend toujours entre 20 et 100 ms en conditions réelles — et chaque ressource bloquante en ajoute un. Un site qui enchaîne trois requêtes séquentielles avant d’afficher quoi que ce soit sera lent, même sur la fibre.
Et puis il y a les cas qu’on oublie trop facilement depuis nos bureaux parisiens : le métro entre Châtelet et Gare du Nord (bonne chance avec votre 5G), la 4G saturée un samedi à Châtelet-les-Halles, les zones rurales, les téléphones Android à 150 € qui représentent encore la majorité du trafic mondial. Quand on teste exclusivement sur un iPhone 17 en Wi-Fi 6, on ne teste pas grand-chose.
Les navigateurs optimisent, mais ne font pas de miracles
Le lazy loading natif, le prefetch, les service workers — les navigateurs ont ajouté des outils puissants. Mais ils ne peuvent pas compenser un bundle JavaScript de 2 Mo qui bloque le thread principal pendant 800 ms. Le navigateur exécute ce qu’on lui donne. Si on lui donne du code lourd, il rame — poliment, mais il rame.
J’ai récemment audité le site d’un client qui utilisait un framework CSS avec 340 Ko de styles non-utilisés, un widget de chat de 200 Ko, et un consent manager qui chargeait trois scripts tiers avant même d’afficher la première ligne de texte. Résultat : 4,2 secondes de LCP sur mobile. En 2026.
Ce que mesurent vraiment les Core Web Vitals
Google a simplifié ses métriques au fil des ans. En 2026, on se concentre sur trois indicateurs :
- LCP (Largest Contentful Paint) — le temps avant que le contenu principal soit visible. Seuil : 2,5 secondes. C’est le plus intuitif : est-ce que l’utilisateur voit quelque chose rapidement ?
- INP (Interaction to Next Paint) — la réactivité aux interactions utilisateur. Remplaçant du FID depuis mars 2024, c’est devenu la métrique la plus exigeante. Seuil : 200 ms. Chaque clic, chaque tap doit produire un retour visuel en moins de 200 millisecondes.
- CLS (Cumulative Layout Shift) — la stabilité visuelle. Ces décalages de mise en page agaçants qui font cliquer au mauvais endroit parce qu’une pub ou une image s’est chargée entre-temps. Seuil : 0,1.
Ce ne sont pas des métriques techniques abstraites. Elles mesurent l’expérience ressentie par l’utilisateur. Est-ce que la page s’affiche vite ? Est-ce qu’elle répond quand je clique ? Est-ce qu’elle bouge sous mes doigts ? Si la réponse est non à l’une de ces questions, l’utilisateur le sent — même s’il ne sait pas l’expliquer.
Pourquoi on s’acharne — pour de vrai
Le SEO, évidemment
Autant être direct : les Core Web Vitals sont un signal de classement Google. Pas le seul, pas le plus important, mais un signal quand même — et un signal de départage. Sur des requêtes concurrentielles, à contenu équivalent, la page la plus rapide gagne. On a vu des gains mesurables de positionnement après optimisation sur des sites clients. Pas de +50 positions du jour au lendemain, mais des progressions régulières de quelques places sur des requêtes où justement tout se joue à la marge.
La conversion, surtout
Chaque 100 ms de temps de chargement en plus, c’est environ 1 % de conversion en moins. Ce n’est pas nous qui le disons — c’est documenté par Google, Akamai, Amazon, et à peu près tous les acteurs du e-commerce depuis dix ans. En 2026, les utilisateurs sont encore plus impatients qu’avant. Ils ne comparent pas votre site à “un site en 2015”. Ils le comparent à l’app qu’ils viennent de quitter.
Le signal de qualité
C’est le bénéfice le moins évident mais le plus durable. Un site rapide, c’est un site bien construit. Optimiser les Web Vitals, c’est inévitablement nettoyer le code, réduire les dépendances inutiles, structurer l’ordre de chargement. Les gains vont au-delà de la performance : moins de dette technique, moins de bugs obscurs, une base de code plus maintenable. Chaque fois que j’ai travaillé sur les performances d’un projet, j’ai trouvé des problèmes qu’on n’avait pas vus autrement.
L’accessibilité
Un site performant est un site plus accessible. Les utilisateurs sur des appareils modestes ou des connexions lentes sont souvent les mêmes qui ont le plus besoin du service — l’administration, la santé, les services publics. Optimiser, c’est aussi une question d’inclusion. Ce n’est pas un argument marketing. C’est une responsabilité.
Ce qu’on a fait concrètement sur notre site
On a reconstruit notre site sur Astro — un framework qui génère du HTML statique par défaut et n’envoie du JavaScript que quand c’est strictement nécessaire. Voici le journal de bord de l’optimisation, avec les choix qui ont eu le plus d’impact.
Zéro JavaScript par défaut
C’est le principe fondamental d’Astro : chaque page est du HTML pur. Les composants interactifs — un curseur custom, une animation au scroll — sont des “îles” qui s’hydratent de manière ciblée. Le reste, c’est du HTML et du CSS. Pas de runtime React de 40 Ko pour afficher du texte.
Résultat concret : notre INP est à zéro sur les pages de contenu. Pas de JavaScript = pas d’interaction bloquée = pas de jank.
La stratégie des fonts — le détail qui change tout
Les fonts sont un piège classique pour le LCP. Une font non chargée, c’est soit un texte invisible pendant une à deux secondes (FOIT — Flash of Invisible Text), soit un texte qui saute quand la font arrive enfin (FOUT — Flash of Unstyled Text). Dans les deux cas, l’utilisateur le voit, et le CLS en prend un coup.
La solution classique, c’est de pointer vers Google Fonts avec un <link> dans le <head>. Ça marche, mais ça implique deux connexions DNS supplémentaires (fonts.googleapis.com puis fonts.gstatic.com), un aller-retour pour récupérer le CSS, puis un autre pour chaque fichier de font. Sur une connexion moyenne, on perd facilement 200 à 400 ms rien qu’en réseau. Sans compter les questions de RGPD — chaque requête vers Google Fonts envoie l’IP de vos visiteurs chez Google.
De fontless à la Fonts API native d’Astro 6
On a d’abord utilisé fontless, un plugin Vite qui télécharge les fonts Google au build et génère des @font-face optimisés avec fallbacks métriques via fontaine. L’idée est excellente — self-hosting automatique, zéro requête externe, polices système ajustées pour minimiser le CLS.
En pratique, on s’est heurté à un problème structurel : fontless repose sur le hook Vite transformIndexHtml pour injecter les <link rel="preload"> dans le HTML. Or Astro génère son propre HTML — ce hook n’est jamais appelé pendant le build statique. Résultat : malgré preload: true dans la config, aucun preload n’était injecté. On avait aussi un micro-plugin Vite pour forcer font-display: optional sur Newsreader, qui fonctionnait de manière intermittente selon le mode d’inlining CSS. Ce n’est pas un bug de fontless — c’est une incompatibilité architecturale entre les plugins Vite pensés pour les SPA et le modèle MPA d’Astro. Le même problème est documenté pour SvelteKit et React Router.
Avec Astro 6.0, le framework embarque une Fonts API native qui fait exactement ce que fontless faisait, mais intégrée au pipeline de build :
import { defineConfig, fontProviders } from 'astro/config';
export default defineConfig({
fonts: [
{
name: 'DM Mono',
cssVariable: '--font-dm-mono',
provider: fontProviders.google(),
weights: [400, 500],
fallbacks: ['monospace'],
},
{
name: 'Bebas Neue',
cssVariable: '--font-bebas-neue',
provider: fontProviders.google(),
weights: [400],
fallbacks: ['sans-serif'],
},
{
name: 'Newsreader',
cssVariable: '--font-newsreader',
provider: fontProviders.google(),
weights: [400],
styles: ['normal', 'italic'],
fallbacks: ['serif'],
},
],
});
Chaque font est ensuite chargée dans le <head> du layout via le composant <Font /> :
---
import { Font } from 'astro:assets';
---
<head>
<Font cssVariable="--font-dm-mono" />
<Font cssVariable="--font-bebas-neue" />
<Font cssVariable="--font-newsreader" />
</head>
Le résultat est propre : les @font-face sont injectés en <style> inline dans le <head> (découverte instantanée par le navigateur), les fallbacks métriques sont générés automatiquement (ascent-override, descent-override, size-adjust), et les fonts sont self-hosted sous /_astro/fonts/ avec des headers de cache immutables. Plus besoin de plugin Vite custom, plus de hack transformIndexHtml, plus de duplication de @font-face à travers les chunks CSS. Et dans le CSS, on référence les fonts via des variables : font-family: var(--font-dm-mono) au lieu de font-family: 'DM Mono', monospace.
Le JavaScript qui sait attendre
Les animations décoratives — la texture de fond qui suit la souris, le curseur custom — ne sont pas critiques. On les diffère avec requestIdleCallback :
if ('requestIdleCallback' in window) {
requestIdleCallback(initTextureMouse, { timeout: 2000 });
} else {
setTimeout(initTextureMouse, 1000);
}
Le navigateur lance ces scripts quand il n’a rien de mieux à faire. En pratique, ça repousse l’exécution de quelques centaines de millisecondes — le temps que le contenu soit affiché et interactif. L’utilisateur ne remarque rien, mais le INP s’en porte beaucoup mieux.
On va même plus loin : ces animations ne tournent que sur Chromium desktop. Sur Safari (où elles consomment plus de GPU) et sur mobile (où elles n’apportent rien sur écran tactile), on les désactive purement et simplement. Pas de demi-mesure.
Prefetch au viewport
Une ligne dans la config Astro, un gros impact sur la navigation :
prefetch: {
defaultStrategy: 'viewport',
},
Tous les liens visibles à l’écran sont préchargés en arrière-plan. Quand l’utilisateur clique, la page est déjà en cache. La navigation interne est quasi instantanée — on mesure des temps de transition sous les 100 ms. C’est le genre d’optimisation invisible qui donne l’impression que le site est “nerveux”.
Les passive listeners et le requestAnimationFrame
Un détail d’implémentation qui compte plus qu’on ne croit : chaque scroll listener du site est déclaré en { passive: true }. Ça indique au navigateur qu’on ne va pas appeler preventDefault(), ce qui lui permet d’optimiser le défilement sans attendre notre callback.
Combiné avec un throttle par requestAnimationFrame, on obtient des animations au scroll (parallax, révélation progressive) à 60 fps sans bloquer le thread principal :
window.addEventListener('scroll', () => {
requestAnimationFrame(updateParallax);
}, { passive: true });
C’est le genre de pattern qu’on ne voit pas dans les tutos mais qui fait la différence entre un score Lighthouse à 95 et un score à 100.
IntersectionObserver partout
Plutôt que de calculer les positions des éléments à chaque scroll (bonjour le layout thrashing), on utilise IntersectionObserver pour déclencher les animations d’entrée :
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target); // libère la mémoire
}
});
}, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' });
Le unobserve après déclenchement est important : une fois l’élément révélé, on arrête de l’observer. Moins d’observers actifs = moins de travail pour le navigateur.
HTML compressé, CSS scopé
Deux lignes dans la config, zéro effort, quelques Ko en moins sur chaque page :
compressHTML: true,
scopedStyleStrategy: 'class',
Le compressHTML supprime les espaces inutiles du HTML généré. Le scopedStyleStrategy: 'class' utilise des classes CSS pour le scoping des styles Astro au lieu de sélecteurs :where() — plus prévisible et marginalement plus performant.
Les résultats
Après tout ça : LCP sous la seconde, INP à zéro, CLS à zéro. Quatre fois 100 dans Lighthouse. Les confettis.
Mais au-delà des chiffres, c’est l’expérience de navigation qui change. Le site répond. Les pages apparaissent. Les animations suivent le scroll sans accrocher. On ne peut pas le mesurer dans un rapport, mais on le sent.
Le mot de la fin
Optimiser les Core Web Vitals en 2026, ce n’est pas de l’obstination de développeur perfectionniste. C’est un investissement mesurable — en SEO, en conversion, en qualité technique. C’est aussi, pour être honnête, un exercice d’ingénierie qui force à comprendre comment fonctionne le navigateur en profondeur. On en ressort meilleur développeur.
Les réseaux sont plus rapides, oui. Les navigateurs sont meilleurs, oui. Mais les attentes des utilisateurs ont augmenté encore plus vite. La barre monte. Et ceux qui la franchissent ont un avantage concret sur ceux qui se contentent de “ça charge, c’est bon”.
On préfère voir les feux d’artifice.