PageSpeed 54 → 91 en une journée — et aucune des vraies causes n'était « compressez vos images »
Mon site affichait 54 sur PageSpeed Insights. Le soir même : 91, accessibilité et bonnes pratiques à 100. Les quatre vraies causes ne figurent dans aucune checklist générique — et l'une d'elles était mon navigateur.
Le rapport qui fait mal (et pourquoi il mentait à moitié)
Ce matin, PageSpeed Insights m'a servi un 54 en performance mobile pour mon propre site. Pour quelqu'un qui vend des sites « performants », c'est le genre de chiffre qu'on préfère découvrir soi-même avant qu'un client le fasse. Le soir même, le même test affichait 91 en performance, 100 en accessibilité, 100 en bonnes pratiques. Une journée de travail, quatre causes réelles — et je te préviens tout de suite : aucune n'était « compressez vos images », « activez la mise en cache » ou les autres refrains des checklists recyclées depuis 2019.
Mais avant de corriger quoi que ce soit, j'ai dû corriger la mesure elle-même. Deux pièges dans le rapport initial. Un : je testais pascalpotvin.com au lieu de www.pascalpotvin.com — la redirection 301 vers le domaine canonique coûtait 780 ms au chronomètre avant même le premier octet de la page. Teste toujours l'URL finale, pas celle qui redirige. Deux : mon score « bonnes pratiques » de 54 venait presque entièrement… de mes extensions Chrome. Le rapport listait des erreurs console d'un domaine que mon site n'appelle jamais et environ 14 Mo de JavaScript injecté par des extensions — wallets crypto, outils de capture, bloqueur de pub. Lighthouse le dit d'ailleurs en toutes lettres dans ses avertissements, que personne ne lit : les extensions faussent l'audit. En navigation privée, la même page passait à 100. Première leçon, gratuite : avant de « réparer » ton site, assure-toi que le problème n'est pas ton navigateur.
Piège nº 1 : ton contenu n'existe pas avant l'hydratation
Le vrai coupable du 54, lui, était bien dans mon code. Le LCP — Largest Contentful Paint, le moment où le plus gros élément visible finit de peindre — plafonnait à 6,6 secondes, et le détail du rapport montrait un « render delay » de 91 %. Traduction : le HTML arrivait vite, mais l'élément restait invisible pendant des secondes. Pourquoi? Mes sections animées à l'entrée dans le viewport.
Le patron classique : un composant AnimatedSection qui rend son contenu à opacity: 0, attend que l'IntersectionObserver le voie, puis anime vers opacity: 1. Élégant à l'écran, catastrophique au chargement : côté serveur, l'état initial est « caché », donc le HTML livré contient toute la page… invisible. Rien ne s'affiche avant que React s'hydrate et que l'observer se déclenche — sur un mobile simulé lent, ça repousse le premier rendu réel de plusieurs secondes. La correction tient en une idée : le serveur rend visible, et c'est le client qui décide, après montage, de cacher uniquement ce qui est sous le pli pour l'animer au scroll.
// useInView — trois phases au lieu de deux
const [phase, setPhase] = useState("ssr"); // SSR : visible, sans animation
useEffect(() => {
const el = ref.current;
// déjà dans le viewport initial (hero) : rester visible, ne pas animer
if (el.getBoundingClientRect().top < window.innerHeight) return;
setPhase("hidden"); // sous le pli : caché, animera à l'entrée
// … IntersectionObserver → setPhase("visible")
}, []);L'effet visuel au scroll est intact. La différence : un visiteur (ou un robot) qui charge la page voit le contenu immédiatement. Ce seul correctif a ramené le LCP de 6,6 à 4,4 secondes. Si ton site utilise Framer Motion, AOS, ou n'importe quelle lib de « reveal on scroll », vérifie ton HTML serveur : si ton hero y est en opacity: 0, tu paies exactement cette taxe.
Piège nº 2 : un titre en fondu ne « compte » pas
Deuxième découverte, plus contre-intuitive. Après le premier correctif, le LCP restait bloqué vers 4,3 secondes, attribué à un élément improbable : un chiffre de ma section statistiques, à peine visible en bas du viewport. Mon vrai titre — le gros h1 du hero — n'était même pas candidat. La raison : il apparaissait en fondu, via une animation CSS qui part d'opacity: 0 avec un délai de 250 ms.
Règle que presque personne ne connaît : pour le LCP, un élément à opacité zéro n'est pas « peint ». Tant que ton titre fond au noir, le navigateur considère qu'il n'existe pas, et la métrique s'accroche au plus gros élément réellement visible — chez moi, un malheureux « 20+ » de la section stats, peint tard. Le compromis que j'ai retenu : le h1 glisse sans fondre (animation transform seulement, qui ne pénalise pas le LCP), et le reste du hero garde sa cascade de fondus. Résultat mesuré en throttling réel : LCP à 1,7 seconde, égal au FCP, attribué au bon élément. Sur un test sans bridage, 116 millisecondes.
Les animations transform et les animations opacity ne sont pas égales devant les Core Web Vitals. Un élément qui glisse est peint dès la première frame ; un élément qui fond n'existe qu'à la fin du fondu. Réserve les fondus d'entrée aux éléments secondaires, jamais au bloc de texte ou à l'image la plus grosse de ton viewport.
Piège nº 3 : les polices que tu ne précharges pas te re-peignent
Troisième couche de l'oignon. Mon site charge trois familles : Geist pour les titres, Geist Mono pour les étiquettes techniques, Newsreader pour les italiques éditoriales. Par optimisation prématurée, j'avais mis preload: false sur les deux dernières — « elles sont secondaires », pensais-je. Sauf que les italiques Newsreader sont dans le h1, et les étiquettes mono sont partout au-dessus du pli. Ces polices arrivaient tard, le navigateur re-peignait les blocs concernés à leur arrivée, et chaque repaint tardif repoussait le LCP et créait un micro-décalage de mise en page.
Le correctif est une ligne par police dans next/font : preload: true pour tout ce qui apparaît au-dessus du pli. Coût : deux fichiers woff2 (~30 Ko) demandés plus tôt. Gain : plus aucun repaint tardif, et le décalage de mise en page de ma section stats a disparu du rapport. La règle générale : si une police est visible dans le premier écran, elle se précharge. « Police secondaire » est une notion de hiérarchie visuelle, pas de chargement.
Piège nº 4 : 534 lignes de JavaScript pour une page statique
Dernière étape, la plus structurante : ma page d'accueil était un unique composant client de 534 lignes — hero, services, portfolio, blogue, contact, tout hydraté côté navigateur, alors que 90 % de ce contenu ne change jamais et n'a aucune interactivité. C'est le défaut par défaut de beaucoup de sites Next.js : un "use client" en haut du fichier posé un jour de pressé, et toute la page embarque dans le bundle.
Le découpage en composants serveur m'a pris une heure : le contenu passe côté serveur, et seuls trois îlots restent client — le scroll vers les ancres, les filtres de la section blogue, le formulaire de contact chargé paresseusement. Le Total Blocking Time est passé de 290 à 120 ms, et le HTML livré contient désormais tout le contenu, ce qui ne sert pas que la performance : les crawlers d'IA qui ne rendent pas le JavaScript lisent maintenant la page complète — j'ai expliqué pourquoi ça compte dans mon article sur l'AEO.
Ce que « 91 » veut dire (et ne veut pas dire)
Un mot d'honnêteté sur les chiffres, parce que l'industrie entretient la confusion. Le score PageSpeed « lab » est une simulation : Lighthouse charge ta page puis modélise un mobile milieu de gamme sur réseau lent (le mode « Lantern »), et ce modèle est notoirement pessimiste sur les pages qui embarquent du JavaScript — mon LCP simulé affiche encore 3,5 s alors que la mesure en bridage réel donne 1,7 s et le terrain, 0,2 s. Les scores fluctuent aussi d'un test à l'autre : mon premier passage post-correctifs a donné 75, à cause d'un premier octet à 745 ms sur un démarrage à froid ; le second, 91. Un score n'est pas une constante physique, c'est un échantillon.
Ce qui compte pour ton référencement, ce sont les Core Web Vitals de terrain, mesurés sur tes vrais visiteurs Chrome : LCP sous 2,5 s, INP sous 200 ms (la métrique d'interactivité qui a remplacé FID le 12 mars 2024), CLS sous 0,1 — au 75e percentile. En mai 2026, 55,9 % des origines suivies par le Chrome UX Report passaient les trois. Si ton site est petit, PageSpeed affichera « données insuffisantes » dans cette section : pas un bug, juste un manque de trafic, et une raison de plus de ne pas vivre et mourir par le score lab.
Ma méthode, dans l'ordre
Si ton score fait mal, voici l'ordre d'investigation qui m'a servi — c'est un arbre de décision, pas une checklist. D'abord, fiabilise la mesure : URL canonique sans redirection, navigation privée ou outil hébergé, deux passages. Ensuite, ouvre le détail du LCP : quel élément, et quelle phase domine? Un « render delay » massif pointe vers du contenu caché par JavaScript ou par une animation d'opacité — pas vers tes images. Puis regarde tes polices : tout ce qui est au-dessus du pli est-il préchargé? Enfin seulement, attaque le JavaScript : ce qui n'a pas d'interactivité n'a rien à faire dans un composant client. Les images, la compression, le cache? Vérifie-les, bien sûr — mais en 2026, avec un framework moderne et un CDN, ce n'est presque jamais là que se cache un 54.
Sources
- Interaction to Next Paint devient un Core Web Vital le 12 mars 2024 — web.dev
- Comment les seuils des Core Web Vitals ont été définis — web.dev
- Taux de passage CWV, CrUX mai 2026 — Digital Applied, benchmarks 2026
- Mesures : Lighthouse 12 (modes simulate et devtools), PageSpeed Insights, 4 juillet 2026, sur www.pascalpotvin.com
Site statique vs WordPress : pourquoi l'IA change complètement la donne
WordPress domine le web depuis 20 ans, mais l'arrivée de l'IA redéfinit les règles du jeu. Les sites statiques et les frameworks modernes comme React offrent un terrain de jeu idéal pour l'intelligence artificielle — et ça change tout pour les entreprises.
Les 4 patterns React qui survivent à 18 mois de croissance produit (et les 3 qui s'effondrent)
Un projet React propre peut devenir un enchevêtrement en quelques mois. Voici les 4 patterns de composition qui tiennent à la croissance — et les 3 qui s'effondrent à coup sûr, avec React 19.
15 micro-interactions CSS sans une ligne de JavaScript (ce qui exigeait une lib hier)
Le soulignement qui glisse, la carte qui bascule en 3D, l'entrée animée sans display: none bricolé : 15 micro-interactions en CSS pur, à 60 fps, sans une ligne de JavaScript.