[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fdBd8GW_UWR_RYsdnWPHA4tMTqjQnOv2pSzdCv_NwWXU":3,"$fPkh2OwKiLHoW0Wf9UZyKg2GXQk1INGu4wsLvEB8IBkQ":24,"$fcTFjTaPPT1sVcnh1wWEGoTWMSaOD4bwmwntkMae6w5s":93},{"_id":4,"slug":5,"__v":6,"author":7,"body":8,"canonical":9,"category":10,"createdAt":11,"date":12,"description":13,"htmlContent":14,"image":15,"imageAlt":15,"readingTime":16,"tags":17,"title":22,"updatedAt":23},"6a250954aa6b273b0c8358fe","refonte-typo-variable-font-lazy-load-qui-degrade-les-core-web-vitals",0,"Equipe Seogard","# Refonte typo : quand une variable font lazy-loadée fait plonger le LCP et le ranking\n\nMercredi 14h. L'équipe design valide la dernière maquette. La refonte typographique est bouclée : exit la classique Inter servie par Google Fonts, place à une variable font custom auto-hébergée. Trois fichiers `.woff2`, un `@font-face` propre, un rendu sublime sur Figma. Le site — une marketplace de mobilier avec 4 200 fiches produit et 380 000 sessions organiques mensuelles — déploie le changement jeudi soir. Aucune balise SEO modifiée. Aucune URL touchée. Aucun redirect. Juste une police. Le genre de changement que personne ne monitore.\n\n## T+72h — Le signal faible que personne ne voit\n\nLe vendredi qui suit le déploiement, rien ne bouge. Ni dans la Search Console, ni dans GA4. Normal : les données CWV mettent 28 jours à se consolider dans le rapport Chrome UX (CrUX), et la Search Console agrège les métriques sur des fenêtres glissantes.\n\nLe lundi suivant, un développeur frontend remarque un flash de texte au chargement. FOUT — Flash of Unstyled Text. La police system fallback s'affiche pendant une bonne seconde avant que la variable font prenne le relais. Il ouvre un ticket Jira, priorité basse. \"Cosmétique.\"\n\nLe mercredi, T+6 jours, le lead SEO lance son audit hebdomadaire dans PageSpeed Insights. Il teste la homepage. LCP : 3.1 secondes. La semaine précédente, le même test affichait 1.8 secondes. Écart de 1.3 secondes. Il vérifie : même connexion, même machine, même heure. Il relance trois fois. LCP moyen : 3.05s.\n\nIl bascule sur une fiche produit. LCP : 2.9s. Avant : 1.7s. L'écart est constant, autour de 1.2 seconde.\n\nPremier réflexe : vérifier si un script tiers a été ajouté. Le tag manager est identique. Pas de nouveau pixel. Pas de changement CDN. Le TTFB reste stable à 340ms. Le CLS est à 0.04, inchangé.\n\nDeuxième réflexe : ouvrir le rapport Core Web Vitals de la Search Console. Les données terrain (field data) montrent encore les anciens chiffres — elles sont agrégées sur 28 jours. Mais les données lab de Lighthouse sont formelles : le LCP a décroché.\n\nL'équipe commence à chercher un changement d'infrastructure. Quelqu'un vérifie les headers de cache côté Cloudflare. Tout est en ordre. Un autre regarde les logs Nginx. RAS.\n\nC'est le développeur frontend — celui du ticket FOUT — qui fait le lien en standup le jeudi matin. \"La nouvelle police. Elle se charge tard.\" Le lead SEO ouvre Chrome DevTools, onglet Network, filtre sur `font`. Ce qu'il voit le fait blêmir.\n\nLa variable font — 287 Ko en `.woff2` — se charge en cascade, déclenchée par le parsing CSS, lui-même lazy-loadé via un `\u003Clink rel=\"stylesheet\" media=\"print\" onload=\"this.media='all'\">`. Le navigateur ne découvre la police qu'après avoir chargé et parsé le CSS critique. La police est invisible pour le preload scanner. Le LCP element — le titre H1 de la fiche produit — attend la font pour être considéré comme \"rendu final\" par le navigateur.\n\nLe diagnostic initial (\"c'est un problème de CDN\") était faux. Le problème, c'est un pattern d'optimisation CSS qui a transformé la font en ressource invisible.\n\n## Le bug : un pattern d'optimisation qui se retourne contre le LCP\n\nPour comprendre la régression, il faut retracer la chaîne de chargement complète.\n\n### Le pattern CSS \"print trick\"\n\nL'équipe utilisait un pattern classique pour charger le CSS de manière non bloquante :\n\n```html\n\u003Clink\n  rel=\"stylesheet\"\n  href=\"/css/main.css\"\n  media=\"print\"\n  onload=\"this.media='all'\"\n/>\n\u003Cnoscript>\n  \u003Clink rel=\"stylesheet\" href=\"/css/main.css\" />\n\u003C/noscript>\n```\n\nCe pattern, recommandé par Google il y a quelques années pour réduire le render-blocking CSS, force le navigateur à ignorer la feuille de style au chargement initial (car `media=\"print\"` ne concerne pas l'écran). Une fois le fichier téléchargé en arrière-plan, l'attribut `onload` bascule le media sur `all`, et les styles s'appliquent.\n\nAvec l'ancienne police Google Fonts, ça n'avait aucun impact sur le LCP : la font était chargée via un `\u003Clink rel=\"preconnect\">` séparé, et Google Fonts injectait sa propre logique de preload.\n\nMais la refonte a changé la donne. La `@font-face` de la nouvelle variable font est déclarée **dans** `main.css` :\n\n```css\n/* main.css */\n@font-face {\n  font-family: 'Mabry Pro VF';\n  src: url('/fonts/mabry-pro-variable.woff2') format('woff2-variations');\n  font-weight: 100 900;\n  font-style: normal;\n  font-display: swap;\n}\n\nbody {\n  font-family: 'Mabry Pro VF', system-ui, sans-serif;\n}\n```\n\n### La cascade fatale\n\nVoici ce qui se passe du point de vue du navigateur, étape par étape :\n\n1. Le HTML se charge. Le parser rencontre le `\u003Clink media=\"print\">`.\n2. Le navigateur télécharge `main.css` en priorité basse (c'est du `print`).\n3. Le fichier CSS arrive. L'event `onload` bascule le media sur `all`.\n4. Le navigateur parse le CSS. Il découvre la `@font-face`.\n5. Il lance le téléchargement de `mabry-pro-variable.woff2` (287 Ko).\n6. Pendant ce temps, le H1 s'affiche en `system-ui` (fallback). C'est le FOUT.\n7. La font arrive. Le navigateur re-rend le H1 avec Mabry Pro VF.\n8. **C'est ce re-rendu qui constitue le LCP final** selon l'algorithme Chromium.\n\nLe preload scanner du navigateur — le mécanisme qui scanne le HTML brut pour lancer les téléchargements en avance — ne peut pas voir la font. Elle est déclarée dans un fichier CSS externe, lui-même masqué derrière `media=\"print\"`. Double invisibilité.\n\n### Ce que voit le développeur vs ce que mesure Lighthouse\n\nLe développeur voit la page s'afficher \"immédiatement\" : le fallback `system-ui` apparaît en moins de 500ms. Visuellement, la page est là. Le FOUT dure une seconde, puis la vraie police s'installe. Pour l'œil humain, c'est un détail.\n\nMais pour l'algorithme LCP de Chromium, c'est une autre histoire. Le LCP element est le H1. Quand la font change, le navigateur émet un **nouveau LCP candidate**. Le timestamp final du LCP devient celui du re-rendu avec la font définitive — pas celui du rendu initial en fallback.\n\nLa vérification dans Chrome DevTools :\n\n```bash\n# Dans la console Chrome DevTools, onglet Performance\n# Après un enregistrement de chargement de page :\n# 1. Chercher l'entrée \"Largest Contentful Paint\" dans le timeline\n# 2. Vérifier le \"LCP candidate\" — il pointe vers le re-rendu du H1\n\n# Ou via l'API PerformanceObserver en console :\nnew PerformanceObserver((list) => {\n  for (const entry of list.getEntries()) {\n    console.log('LCP candidate:', entry.startTime, entry.element);\n  }\n}).observe({ type: 'largest-contentful-paint', buffered: true });\n```\n\nSur la fiche produit testée, la console affichait deux LCP candidates :\n- **T+480ms** : le H1 en `system-ui` (fallback)\n- **T+2 940ms** : le même H1 re-rendu en `Mabry Pro VF`\n\nC'est le second qui compte. 2.94 secondes. Au-dessus du seuil \"needs improvement\" de 2.5 secondes fixé par Google.\n\n### Pourquoi les tests n'ont rien détecté\n\nL'équipe avait un pipeline Lighthouse CI dans GitHub Actions. Mais la configuration utilisait une font system en fallback pour les tests — un mock CSS qui court-circuitait le chargement de la variable font. Le Lighthouse CI passait au vert avec un LCP de 1.1s. En production, le vrai fichier font de 287 Ko changeait tout.\n\nAutre point : les tests étaient exécutés sur un réseau local simulant du 4G rapide (9 Mbps). Sur du 4G réel à 3-4 Mbps — le réseau moyen des utilisateurs mobiles français — le téléchargement de la font prenait 600ms de plus.\n\nL'absence de test sur les vraies conditions réseau a masqué la régression pendant trois semaines complètes. Le temps que les données CrUX basculent de \"bon\" à \"à améliorer\", le mal était fait.\n\n### L'impact sur le ranking\n\nLes données CrUX ont basculé au jour 21. Le rapport Core Web Vitals de la Search Console est passé au jaune sur 3 800 URLs (le template fiche produit). Quatre jours plus tard, les premières baisses de positions sont apparues dans les SERPs.\n\nLe lead SEO a mesuré :\n- **−18% de clics organiques** sur les fiches produit en 14 jours (de 12 400 clics/jour à 10 200)\n- **42 mots-clés** passés de la position 3-5 à la position 8-12\n- **Aucun changement** sur les pages catégories (LCP element = image, pas de texte stylé en variable font)\n\nL'ironie : aucune balise title, aucune meta description, aucun canonical, aucun heading n'avait changé. Le contenu était identique, octet pour octet. Seule la performance avait bougé. Et Google l'a sanctionné.\n\n## Le fix : preload explicite et restructuration du chargement\n\nLe correctif a été déployé en deux phases.\n\n### Phase 1 — Le patch immédiat (jour 0)\n\nAjout d'un `\u003Clink rel=\"preload\">` pour la font directement dans le `\u003Chead>`, avant toute feuille de style :\n\n```html\n\u003Chead>\n  \u003C!-- Preload de la variable font AVANT le CSS -->\n  \u003Clink\n    rel=\"preload\"\n    href=\"/fonts/mabry-pro-variable.woff2\"\n    as=\"font\"\n    type=\"font/woff2\"\n    crossorigin\n  />\n\n  \u003C!-- CSS critique inline -->\n  \u003Cstyle>\n    @font-face {\n      font-family: 'Mabry Pro VF';\n      src: url('/fonts/mabry-pro-variable.woff2') format('woff2-variations');\n      font-weight: 100 900;\n      font-style: normal;\n      font-display: swap;\n    }\n    body {\n      font-family: 'Mabry Pro VF', system-ui, sans-serif;\n    }\n  \u003C/style>\n\n  \u003C!-- CSS non-critique en async -->\n  \u003Clink\n    rel=\"stylesheet\"\n    href=\"/css/main.css\"\n    media=\"print\"\n    onload=\"this.media='all'\"\n  />\n\u003C/head>\n```\n\nTrois changements clés :\n\n1. **`rel=\"preload\"`** : le preload scanner voit la font dès le parsing du HTML. Le téléchargement commence immédiatement, en parallèle de tout le reste.\n\n2. **`@font-face` inline dans le critical CSS** : la déclaration de font n'est plus enfermée dans le fichier CSS lazy-loadé. Le navigateur sait qu'il a besoin de cette font dès le premier rendu.\n\n3. **`crossorigin` obligatoire** : sans cet attribut, le preload et le `@font-face` font deux requêtes séparées. Le navigateur traite les fonts comme des requêtes CORS anonymes. Si le preload n'a pas `crossorigin`, il télécharge la font une première fois (preload, sans CORS), puis la re-télécharge (font-face, avec CORS). Double pénalité.\n\n### Phase 2 — Optimisation de la font (jour +2)\n\nLa variable font faisait 287 Ko. Après audit avec `pyftsubset`, l'équipe a créé un subset limité aux glyphes Latin + Latin Extended :\n\n```bash\n# Subsetting avec pyftsubset (paquet fonttools)\npyftsubset mabry-pro-variable.woff2 \\\n  --output-file=mabry-pro-variable-subset.woff2 \\\n  --flavor=woff2 \\\n  --layout-features='kern,liga,calt,frac,sups,subs' \\\n  --unicodes='U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD'\n```\n\nRésultat : 287 Ko → 94 Ko. Le téléchargement passe de 420ms à 140ms sur une connexion 4G médiane.\n\n### Phase 3 — Pipeline de test corrigé (jour +3)\n\nL'équipe a ajouté une assertion Lighthouse CI sur le LCP avec les vraies fonts :\n\n```yaml\n# lighthouserc.yml\nci:\n  collect:\n    settings:\n      throttling:\n        cpuSlowdownMultiplier: 4\n        requestLatencyMs: 150\n        downloadThroughputKbps: 1600\n        uploadThroughputKbps: 750\n  assert:\n    assertions:\n      largest-contentful-paint:\n        - error\n        - maxNumericValue: 2500\n      resource-summary:font:count:\n        - warn\n        - maxNumericValue: 3\n      resource-summary:font:size:\n        - warn\n        - maxNumericValue: 150000\n```\n\nLe throttling simule désormais du 4G moyen réel. Et le LCP est un critère bloquant du pipeline : au-dessus de 2.5 secondes, le build échoue.\n\n### La récupération\n\nLe patch preload a été déployé un jeudi soir. Résultats mesurés dans Lighthouse (données lab) dès le lendemain :\n\n- LCP homepage : 3.1s → 1.6s\n- LCP fiche produit : 2.9s → 1.5s\n\nMais les données terrain (CrUX) ont mis **19 jours** à basculer. La Search Console a repassé les URLs en vert au jour 24. Les positions ont commencé à remonter au jour 28.\n\nAu jour 35 après le fix, le trafic organique sur les fiches produit avait récupéré 92% du niveau pré-régression. Les 8% restants se sont résorbés sur les deux semaines suivantes.\n\nBilan : 5 semaines de régression totale (3 semaines avant détection + 2 semaines de déploiement et récupération). Impact estimé : **−52 000 clics organiques perdus** sur la période.\n\nCe type de régression silencieuse — pas de balise modifiée, pas d'URL changée, juste un delta de performance — est le plus difficile à détecter manuellement. C'est aussi le type qui fait le plus de dégâts parce qu'il passe sous tous les radars classiques : [les audits de heading](/blog/refonte-header-le-h1-remplace-par-un-div-title-par-le-design-system), les vérifications de canonicals, les contrôles de redirections. Tout est \"vert\" sauf la vitesse.\n\n## Ce qu'on en retient\n\nUne refonte typographique n'est pas un changement cosmétique. C'est un changement de chaîne de chargement. Chaque ressource qui s'intercale entre le premier octet et le rendu final du LCP element est un risque.\n\nTrois règles à graver :\n\n1. Toute font utilisée par le LCP element doit être en `preload` dans le `\u003Chead>`.\n2. Toute `@font-face` critique doit être inline, pas dans un CSS lazy-loadé.\n3. Le pipeline Lighthouse CI doit tester avec les vraies fonts et un throttling réseau réaliste.\n\nLe problème de fond reste la détection. Cette équipe a mis 21 jours à voir la régression. Un outil de monitoring qui compare le LCP réel — mesuré en conditions terrain, page par page, jour après jour — aurait levé l'alerte en 48 heures. C'est exactement ce que fait [Seogard](https://seogard.io) sur les métriques de performance liées au ranking. Pas un audit ponctuel. Un suivi continu.\n\nLes régressions les plus coûteuses sont celles qui ne cassent rien de visible. Ni le HTML, ni les metas, ni le sitemap. Juste le temps que met une police à s'afficher. 1.2 seconde. 52 000 clics.\n```","https://seogard.io/blog/refonte-typo-variable-font-lazy-load-qui-degrade-les-core-web-vitals","Performance","2026-06-07T06:01:56.599Z","2026-06-07","Une refonte typo charge la police en lazy. Le LCP passe de 1.8s à 3.0s. Aucune meta ne bouge. Le trafic chute de 18%. Récit, diagnostic, fix.","\u003Ch1>Refonte typo : quand une variable font lazy-loadée fait plonger le LCP et le ranking\u003C/h1>\n\u003Cp>Mercredi 14h. L'équipe design valide la dernière maquette. La refonte typographique est bouclée : exit la classique Inter servie par Google Fonts, place à une variable font custom auto-hébergée. Trois fichiers \u003Ccode>.woff2\u003C/code>, un \u003Ccode>@font-face\u003C/code> propre, un rendu sublime sur Figma. Le site — une marketplace de mobilier avec 4 200 fiches produit et 380 000 sessions organiques mensuelles — déploie le changement jeudi soir. Aucune balise SEO modifiée. Aucune URL touchée. Aucun redirect. Juste une police. Le genre de changement que personne ne monitore.\u003C/p>\n\u003Ch2>T+72h — Le signal faible que personne ne voit\u003C/h2>\n\u003Cp>Le vendredi qui suit le déploiement, rien ne bouge. Ni dans la Search Console, ni dans GA4. Normal : les données CWV mettent 28 jours à se consolider dans le rapport Chrome UX (CrUX), et la Search Console agrège les métriques sur des fenêtres glissantes.\u003C/p>\n\u003Cp>Le lundi suivant, un développeur frontend remarque un flash de texte au chargement. FOUT — Flash of Unstyled Text. La police system fallback s'affiche pendant une bonne seconde avant que la variable font prenne le relais. Il ouvre un ticket Jira, priorité basse. \"Cosmétique.\"\u003C/p>\n\u003Cp>Le mercredi, T+6 jours, le lead SEO lance son audit hebdomadaire dans PageSpeed Insights. Il teste la homepage. LCP : 3.1 secondes. La semaine précédente, le même test affichait 1.8 secondes. Écart de 1.3 secondes. Il vérifie : même connexion, même machine, même heure. Il relance trois fois. LCP moyen : 3.05s.\u003C/p>\n\u003Cp>Il bascule sur une fiche produit. LCP : 2.9s. Avant : 1.7s. L'écart est constant, autour de 1.2 seconde.\u003C/p>\n\u003Cp>Premier réflexe : vérifier si un script tiers a été ajouté. Le tag manager est identique. Pas de nouveau pixel. Pas de changement CDN. Le TTFB reste stable à 340ms. Le CLS est à 0.04, inchangé.\u003C/p>\n\u003Cp>Deuxième réflexe : ouvrir le rapport Core Web Vitals de la Search Console. Les données terrain (field data) montrent encore les anciens chiffres — elles sont agrégées sur 28 jours. Mais les données lab de Lighthouse sont formelles : le LCP a décroché.\u003C/p>\n\u003Cp>L'équipe commence à chercher un changement d'infrastructure. Quelqu'un vérifie les headers de cache côté Cloudflare. Tout est en ordre. Un autre regarde les logs Nginx. RAS.\u003C/p>\n\u003Cp>C'est le développeur frontend — celui du ticket FOUT — qui fait le lien en standup le jeudi matin. \"La nouvelle police. Elle se charge tard.\" Le lead SEO ouvre Chrome DevTools, onglet Network, filtre sur \u003Ccode>font\u003C/code>. Ce qu'il voit le fait blêmir.\u003C/p>\n\u003Cp>La variable font — 287 Ko en \u003Ccode>.woff2\u003C/code> — se charge en cascade, déclenchée par le parsing CSS, lui-même lazy-loadé via un \u003Ccode>&#x3C;link rel=\"stylesheet\" media=\"print\" onload=\"this.media='all'\">\u003C/code>. Le navigateur ne découvre la police qu'après avoir chargé et parsé le CSS critique. La police est invisible pour le preload scanner. Le LCP element — le titre H1 de la fiche produit — attend la font pour être considéré comme \"rendu final\" par le navigateur.\u003C/p>\n\u003Cp>Le diagnostic initial (\"c'est un problème de CDN\") était faux. Le problème, c'est un pattern d'optimisation CSS qui a transformé la font en ressource invisible.\u003C/p>\n\u003Ch2>Le bug : un pattern d'optimisation qui se retourne contre le LCP\u003C/h2>\n\u003Cp>Pour comprendre la régression, il faut retracer la chaîne de chargement complète.\u003C/p>\n\u003Ch3>Le pattern CSS \"print trick\"\u003C/h3>\n\u003Cp>L'équipe utilisait un pattern classique pour charger le CSS de manière non bloquante :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">link\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  rel\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"stylesheet\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  href\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/css/main.css\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  media\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"print\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  onload\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#79B8FF\">this\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">media\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'all'\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">/>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">noscript\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">link\u003C/span>\u003Cspan style=\"color:#B392F0\"> rel\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"stylesheet\"\u003C/span>\u003Cspan style=\"color:#B392F0\"> href\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/css/main.css\"\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> />\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">noscript\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Ce pattern, recommandé par Google il y a quelques années pour réduire le render-blocking CSS, force le navigateur à ignorer la feuille de style au chargement initial (car \u003Ccode>media=\"print\"\u003C/code> ne concerne pas l'écran). Une fois le fichier téléchargé en arrière-plan, l'attribut \u003Ccode>onload\u003C/code> bascule le media sur \u003Ccode>all\u003C/code>, et les styles s'appliquent.\u003C/p>\n\u003Cp>Avec l'ancienne police Google Fonts, ça n'avait aucun impact sur le LCP : la font était chargée via un \u003Ccode>&#x3C;link rel=\"preconnect\">\u003C/code> séparé, et Google Fonts injectait sa propre logique de preload.\u003C/p>\n\u003Cp>Mais la refonte a changé la donne. La \u003Ccode>@font-face\u003C/code> de la nouvelle variable font est déclarée \u003Cstrong>dans\u003C/strong> \u003Ccode>main.css\u003C/code> :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">/* main.css */\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">@font-face\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  font-family\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Mabry Pro VF'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  src\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">url\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'/fonts/mabry-pro-variable.woff2'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#79B8FF\">format\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'woff2-variations'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  font-weight\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">100\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 900\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  font-style\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">normal\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  font-display\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">swap\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">body\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  font-family\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Mabry Pro VF'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">system-ui\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">sans-serif\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Ch3>La cascade fatale\u003C/h3>\n\u003Cp>Voici ce qui se passe du point de vue du navigateur, étape par étape :\u003C/p>\n\u003Col>\n\u003Cli>Le HTML se charge. Le parser rencontre le \u003Ccode>&#x3C;link media=\"print\">\u003C/code>.\u003C/li>\n\u003Cli>Le navigateur télécharge \u003Ccode>main.css\u003C/code> en priorité basse (c'est du \u003Ccode>print\u003C/code>).\u003C/li>\n\u003Cli>Le fichier CSS arrive. L'event \u003Ccode>onload\u003C/code> bascule le media sur \u003Ccode>all\u003C/code>.\u003C/li>\n\u003Cli>Le navigateur parse le CSS. Il découvre la \u003Ccode>@font-face\u003C/code>.\u003C/li>\n\u003Cli>Il lance le téléchargement de \u003Ccode>mabry-pro-variable.woff2\u003C/code> (287 Ko).\u003C/li>\n\u003Cli>Pendant ce temps, le H1 s'affiche en \u003Ccode>system-ui\u003C/code> (fallback). C'est le FOUT.\u003C/li>\n\u003Cli>La font arrive. Le navigateur re-rend le H1 avec Mabry Pro VF.\u003C/li>\n\u003Cli>\u003Cstrong>C'est ce re-rendu qui constitue le LCP final\u003C/strong> selon l'algorithme Chromium.\u003C/li>\n\u003C/ol>\n\u003Cp>Le preload scanner du navigateur — le mécanisme qui scanne le HTML brut pour lancer les téléchargements en avance — ne peut pas voir la font. Elle est déclarée dans un fichier CSS externe, lui-même masqué derrière \u003Ccode>media=\"print\"\u003C/code>. Double invisibilité.\u003C/p>\n\u003Ch3>Ce que voit le développeur vs ce que mesure Lighthouse\u003C/h3>\n\u003Cp>Le développeur voit la page s'afficher \"immédiatement\" : le fallback \u003Ccode>system-ui\u003C/code> apparaît en moins de 500ms. Visuellement, la page est là. Le FOUT dure une seconde, puis la vraie police s'installe. Pour l'œil humain, c'est un détail.\u003C/p>\n\u003Cp>Mais pour l'algorithme LCP de Chromium, c'est une autre histoire. Le LCP element est le H1. Quand la font change, le navigateur émet un \u003Cstrong>nouveau LCP candidate\u003C/strong>. Le timestamp final du LCP devient celui du re-rendu avec la font définitive — pas celui du rendu initial en fallback.\u003C/p>\n\u003Cp>La vérification dans Chrome DevTools :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Dans la console Chrome DevTools, onglet Performance\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Après un enregistrement de chargement de page :\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># 1. Chercher l'entrée \"Largest Contentful Paint\" dans le timeline\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># 2. Vérifier le \"LCP candidate\" — il pointe vers le re-rendu du H1\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Ou via l'API PerformanceObserver en console :\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">new\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> PerformanceObserver\u003C/span>\u003Cspan style=\"color:#E1E4E8\">((\u003C/span>\u003Cspan style=\"color:#B392F0\">list\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#9ECBFF\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">> \u003C/span>\u003Cspan style=\"color:#9ECBFF\">{\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  for\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003Cspan style=\"color:#B392F0\">const\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> entry\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> of\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> list.getEntries\u003C/span>\u003Cspan style=\"color:#E1E4E8\">()) {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    console.log(\u003C/span>\u003Cspan style=\"color:#B392F0\">'LCP candidate:'\u003C/span>\u003Cspan style=\"color:#B392F0\">,\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> entry.startTime,\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> entry.element\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">}).observe({ type: 'largest-contentful-paint', buffered: true });\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Sur la fiche produit testée, la console affichait deux LCP candidates :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>T+480ms\u003C/strong> : le H1 en \u003Ccode>system-ui\u003C/code> (fallback)\u003C/li>\n\u003Cli>\u003Cstrong>T+2 940ms\u003C/strong> : le même H1 re-rendu en \u003Ccode>Mabry Pro VF\u003C/code>\u003C/li>\n\u003C/ul>\n\u003Cp>C'est le second qui compte. 2.94 secondes. Au-dessus du seuil \"needs improvement\" de 2.5 secondes fixé par Google.\u003C/p>\n\u003Ch3>Pourquoi les tests n'ont rien détecté\u003C/h3>\n\u003Cp>L'équipe avait un pipeline Lighthouse CI dans GitHub Actions. Mais la configuration utilisait une font system en fallback pour les tests — un mock CSS qui court-circuitait le chargement de la variable font. Le Lighthouse CI passait au vert avec un LCP de 1.1s. En production, le vrai fichier font de 287 Ko changeait tout.\u003C/p>\n\u003Cp>Autre point : les tests étaient exécutés sur un réseau local simulant du 4G rapide (9 Mbps). Sur du 4G réel à 3-4 Mbps — le réseau moyen des utilisateurs mobiles français — le téléchargement de la font prenait 600ms de plus.\u003C/p>\n\u003Cp>L'absence de test sur les vraies conditions réseau a masqué la régression pendant trois semaines complètes. Le temps que les données CrUX basculent de \"bon\" à \"à améliorer\", le mal était fait.\u003C/p>\n\u003Ch3>L'impact sur le ranking\u003C/h3>\n\u003Cp>Les données CrUX ont basculé au jour 21. Le rapport Core Web Vitals de la Search Console est passé au jaune sur 3 800 URLs (le template fiche produit). Quatre jours plus tard, les premières baisses de positions sont apparues dans les SERPs.\u003C/p>\n\u003Cp>Le lead SEO a mesuré :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>−18% de clics organiques\u003C/strong> sur les fiches produit en 14 jours (de 12 400 clics/jour à 10 200)\u003C/li>\n\u003Cli>\u003Cstrong>42 mots-clés\u003C/strong> passés de la position 3-5 à la position 8-12\u003C/li>\n\u003Cli>\u003Cstrong>Aucun changement\u003C/strong> sur les pages catégories (LCP element = image, pas de texte stylé en variable font)\u003C/li>\n\u003C/ul>\n\u003Cp>L'ironie : aucune balise title, aucune meta description, aucun canonical, aucun heading n'avait changé. Le contenu était identique, octet pour octet. Seule la performance avait bougé. Et Google l'a sanctionné.\u003C/p>\n\u003Ch2>Le fix : preload explicite et restructuration du chargement\u003C/h2>\n\u003Cp>Le correctif a été déployé en deux phases.\u003C/p>\n\u003Ch3>Phase 1 — Le patch immédiat (jour 0)\u003C/h3>\n\u003Cp>Ajout d'un \u003Ccode>&#x3C;link rel=\"preload\">\u003C/code> pour la font directement dans le \u003Ccode>&#x3C;head>\u003C/code>, avant toute feuille de style :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">head\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  &#x3C;!-- Preload de la variable font AVANT le CSS -->\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">link\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    rel\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"preload\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    href\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/fonts/mabry-pro-variable.woff2\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    as\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"font\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    type\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"font/woff2\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    crossorigin\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  />\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  &#x3C;!-- CSS critique inline -->\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">style\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    @font-face\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">      font-family\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Mabry Pro VF'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">      src\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">url\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'/fonts/mabry-pro-variable.woff2'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">) \u003C/span>\u003Cspan style=\"color:#79B8FF\">format\u003C/span>\u003Cspan style=\"color:#E1E4E8\">(\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'woff2-variations'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">);\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">      font-weight\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">100\u003C/span>\u003Cspan style=\"color:#79B8FF\"> 900\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">      font-style\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">normal\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">      font-display\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">swap\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    body\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">      font-family\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'Mabry Pro VF'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">system-ui\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#79B8FF\">sans-serif\u003C/span>\u003Cspan style=\"color:#E1E4E8\">;\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    }\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  &#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">style\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">  &#x3C;!-- CSS non-critique en async -->\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  &#x3C;\u003C/span>\u003Cspan style=\"color:#85E89D\">link\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    rel\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"stylesheet\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    href\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"/css/main.css\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    media\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"print\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">    onload\u003C/span>\u003Cspan style=\"color:#E1E4E8\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"\u003C/span>\u003Cspan style=\"color:#79B8FF\">this\u003C/span>\u003Cspan style=\"color:#9ECBFF\">.\u003C/span>\u003Cspan style=\"color:#E1E4E8\">media\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'all'\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  />\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">&#x3C;/\u003C/span>\u003Cspan style=\"color:#85E89D\">head\u003C/span>\u003Cspan style=\"color:#E1E4E8\">>\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Trois changements clés :\u003C/p>\n\u003Col>\n\u003Cli>\n\u003Cp>\u003Cstrong>\u003Ccode>rel=\"preload\"\u003C/code>\u003C/strong> : le preload scanner voit la font dès le parsing du HTML. Le téléchargement commence immédiatement, en parallèle de tout le reste.\u003C/p>\n\u003C/li>\n\u003Cli>\n\u003Cp>\u003Cstrong>\u003Ccode>@font-face\u003C/code> inline dans le critical CSS\u003C/strong> : la déclaration de font n'est plus enfermée dans le fichier CSS lazy-loadé. Le navigateur sait qu'il a besoin de cette font dès le premier rendu.\u003C/p>\n\u003C/li>\n\u003Cli>\n\u003Cp>\u003Cstrong>\u003Ccode>crossorigin\u003C/code> obligatoire\u003C/strong> : sans cet attribut, le preload et le \u003Ccode>@font-face\u003C/code> font deux requêtes séparées. Le navigateur traite les fonts comme des requêtes CORS anonymes. Si le preload n'a pas \u003Ccode>crossorigin\u003C/code>, il télécharge la font une première fois (preload, sans CORS), puis la re-télécharge (font-face, avec CORS). Double pénalité.\u003C/p>\n\u003C/li>\n\u003C/ol>\n\u003Ch3>Phase 2 — Optimisation de la font (jour +2)\u003C/h3>\n\u003Cp>La variable font faisait 287 Ko. Après audit avec \u003Ccode>pyftsubset\u003C/code>, l'équipe a créé un subset limité aux glyphes Latin + Latin Extended :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Subsetting avec pyftsubset (paquet fonttools)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">pyftsubset\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> mabry-pro-variable.woff2\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  --output-file=mabry-pro-variable-subset.woff2\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  --flavor=woff2\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  --layout-features=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'kern,liga,calt,frac,sups,subs'\u003C/span>\u003Cspan style=\"color:#79B8FF\"> \\\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">  --unicodes=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD'\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Résultat : 287 Ko → 94 Ko. Le téléchargement passe de 420ms à 140ms sur une connexion 4G médiane.\u003C/p>\n\u003Ch3>Phase 3 — Pipeline de test corrigé (jour +3)\u003C/h3>\n\u003Cp>L'équipe a ajouté une assertion Lighthouse CI sur le LCP avec les vraies fonts :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># lighthouserc.yml\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">ci\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  collect\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    settings\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">      throttling\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        cpuSlowdownMultiplier\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">4\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        requestLatencyMs\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">150\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        downloadThroughputKbps\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">1600\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        uploadThroughputKbps\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">750\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  assert\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    assertions\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">      largest-contentful-paint\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">error\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        - \u003C/span>\u003Cspan style=\"color:#85E89D\">maxNumericValue\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">2500\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">      resource-summary:font:count\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">warn\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        - \u003C/span>\u003Cspan style=\"color:#85E89D\">maxNumericValue\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">3\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">      resource-summary:font:size\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">warn\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        - \u003C/span>\u003Cspan style=\"color:#85E89D\">maxNumericValue\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">150000\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le throttling simule désormais du 4G moyen réel. Et le LCP est un critère bloquant du pipeline : au-dessus de 2.5 secondes, le build échoue.\u003C/p>\n\u003Ch3>La récupération\u003C/h3>\n\u003Cp>Le patch preload a été déployé un jeudi soir. Résultats mesurés dans Lighthouse (données lab) dès le lendemain :\u003C/p>\n\u003Cul>\n\u003Cli>LCP homepage : 3.1s → 1.6s\u003C/li>\n\u003Cli>LCP fiche produit : 2.9s → 1.5s\u003C/li>\n\u003C/ul>\n\u003Cp>Mais les données terrain (CrUX) ont mis \u003Cstrong>19 jours\u003C/strong> à basculer. La Search Console a repassé les URLs en vert au jour 24. Les positions ont commencé à remonter au jour 28.\u003C/p>\n\u003Cp>Au jour 35 après le fix, le trafic organique sur les fiches produit avait récupéré 92% du niveau pré-régression. Les 8% restants se sont résorbés sur les deux semaines suivantes.\u003C/p>\n\u003Cp>Bilan : 5 semaines de régression totale (3 semaines avant détection + 2 semaines de déploiement et récupération). Impact estimé : \u003Cstrong>−52 000 clics organiques perdus\u003C/strong> sur la période.\u003C/p>\n\u003Cp>Ce type de régression silencieuse — pas de balise modifiée, pas d'URL changée, juste un delta de performance — est le plus difficile à détecter manuellement. C'est aussi le type qui fait le plus de dégâts parce qu'il passe sous tous les radars classiques : \u003Ca href=\"/blog/refonte-header-le-h1-remplace-par-un-div-title-par-le-design-system\">les audits de heading\u003C/a>, les vérifications de canonicals, les contrôles de redirections. Tout est \"vert\" sauf la vitesse.\u003C/p>\n\u003Ch2>Ce qu'on en retient\u003C/h2>\n\u003Cp>Une refonte typographique n'est pas un changement cosmétique. C'est un changement de chaîne de chargement. Chaque ressource qui s'intercale entre le premier octet et le rendu final du LCP element est un risque.\u003C/p>\n\u003Cp>Trois règles à graver :\u003C/p>\n\u003Col>\n\u003Cli>Toute font utilisée par le LCP element doit être en \u003Ccode>preload\u003C/code> dans le \u003Ccode>&#x3C;head>\u003C/code>.\u003C/li>\n\u003Cli>Toute \u003Ccode>@font-face\u003C/code> critique doit être inline, pas dans un CSS lazy-loadé.\u003C/li>\n\u003Cli>Le pipeline Lighthouse CI doit tester avec les vraies fonts et un throttling réseau réaliste.\u003C/li>\n\u003C/ol>\n\u003Cp>Le problème de fond reste la détection. Cette équipe a mis 21 jours à voir la régression. Un outil de monitoring qui compare le LCP réel — mesuré en conditions terrain, page par page, jour après jour — aurait levé l'alerte en 48 heures. C'est exactement ce que fait \u003Ca href=\"https://seogard.io\">Seogard\u003C/a> sur les métriques de performance liées au ranking. Pas un audit ponctuel. Un suivi continu.\u003C/p>\n\u003Cp>Les régressions les plus coûteuses sont celles qui ne cassent rien de visible. Ni le HTML, ni les metas, ni le sitemap. Juste le temps que met une police à s'afficher. 1.2 seconde. 52 000 clics.\u003C/p>\n\u003Cpre>\u003Ccode>\u003C/code>\u003C/pre>",null,12,[18,19,20,21],"core web vitals","lcp","font","performance","Variable font lazy-load : LCP dégradé de 1.2s, ranking en chute","Sun Jun 07 2026 06:01:56 GMT+0000 (Coordinated Universal Time)",[25,38,49,60,71,83],{"_id":26,"slug":27,"__v":6,"author":7,"canonical":28,"category":10,"createdAt":29,"date":30,"description":31,"image":15,"imageAlt":15,"readingTime":16,"tags":32,"title":36,"updatedAt":37},"69d1cf1f92ec5b0b5e4966b4","core-web-vitals-impact-reel-sur-le-classement-google","https://seogard.io/blog/core-web-vitals-impact-reel-sur-le-classement-google","2026-04-05T02:55:27.210Z","2026-04-05","Analyse technique des Core Web Vitals (LCP, CLS, INP) et leur influence mesurable sur le ranking Google. Données, cas concrets et optimisations.",[33,19,34,35,21],"core-web-vitals","cls","inp","Core Web Vitals : impact réel sur le classement Google","Sun Apr 05 2026 03:00:33 GMT+0000 (Coordinated Universal Time)",{"_id":39,"slug":40,"__v":6,"author":7,"canonical":41,"category":10,"createdAt":42,"date":30,"description":43,"image":15,"imageAlt":15,"readingTime":16,"tags":44,"title":47,"updatedAt":48},"69d1d00b92ec5b0b5e49f372","lcp-diagnostiquer-et-corriger-un-largest-contentful-paint-lent","https://seogard.io/blog/lcp-diagnostiquer-et-corriger-un-largest-contentful-paint-lent","2026-04-05T02:59:23.279Z","Techniques concrètes pour diagnostiquer et corriger un LCP lent : images, fonts, TTFB, preload, CDN. Exemples de code et scénarios réels.",[19,21,45,46,18],"images","fonts","LCP lent : diagnostiquer et corriger le Largest Contentful Paint","Sun Apr 05 2026 03:03:51 GMT+0000 (Coordinated Universal Time)",{"_id":50,"slug":51,"__v":6,"author":7,"canonical":52,"category":10,"createdAt":53,"date":30,"description":54,"image":15,"imageAlt":15,"readingTime":16,"tags":55,"title":58,"updatedAt":59},"69d1d0de92ec5b0b5e4a73d5","inp-comprendre-et-optimiser-interaction-to-next-paint","https://seogard.io/blog/inp-comprendre-et-optimiser-interaction-to-next-paint","2026-04-05T03:02:54.632Z","Guide technique pour comprendre, mesurer et optimiser INP. Stratégies JavaScript concrètes pour passer sous le seuil des 200ms.",[35,56,57,21,18],"interaction","javascript","INP : diagnostiquer et corriger une Interaction to Next Paint lente","Sun Apr 05 2026 03:07:02 GMT+0000 (Coordinated Universal Time)",{"_id":61,"slug":62,"__v":6,"author":7,"canonical":63,"category":10,"createdAt":64,"date":30,"description":65,"image":15,"imageAlt":15,"readingTime":16,"tags":66,"title":69,"updatedAt":70},"69d1d17c92ec5b0b5e4ad607","cls-identifier-et-eliminer-les-decalages-de-layout","https://seogard.io/blog/cls-identifier-et-eliminer-les-decalages-de-layout","2026-04-05T03:05:32.661Z","Diagnostic technique et correction du Cumulative Layout Shift : causes réelles, outils de mesure, snippets de code et stratégies pour les sites à fort volume.",[34,67,21,68,33],"layout-shift","ux","CLS : identifier et éliminer les décalages de layout","Sun Apr 05 2026 03:10:53 GMT+0000 (Coordinated Universal Time)",{"_id":72,"slug":73,"__v":6,"author":7,"canonical":74,"category":10,"createdAt":75,"date":30,"description":76,"image":15,"imageAlt":15,"readingTime":16,"tags":77,"title":81,"updatedAt":82},"69d1d23f92ec5b0b5e4b4a0f","optimisation-des-images-pour-le-seo-et-la-performance","https://seogard.io/blog/optimisation-des-images-pour-le-seo-et-la-performance","2026-04-05T03:08:47.906Z","Guide technique pour optimiser vos images : formats WebP/AVIF, lazy loading natif, responsive images et configuration serveur. Avec code et cas concret.",[45,78,79,80,21],"webp","avif","lazy-loading","Images SEO : WebP, AVIF, lazy loading et responsive en production","Sun Apr 05 2026 03:15:02 GMT+0000 (Coordinated Universal Time)",{"_id":84,"slug":85,"__v":6,"author":7,"canonical":86,"category":10,"createdAt":87,"date":30,"description":88,"image":15,"imageAlt":15,"readingTime":16,"tags":89,"title":91,"updatedAt":92},"69d1d2d092ec5b0b5e4ba6f5","lazy-loading-bonnes-pratiques-et-pieges-seo","https://seogard.io/blog/lazy-loading-bonnes-pratiques-et-pieges-seo","2026-04-05T03:11:12.800Z","Implémenter le lazy loading sans casser l'indexation : attribut native, Intersection Observer, above-the-fold, et monitoring des régressions.",[80,45,90,57,21],"seo","Lazy loading : bonnes pratiques et pièges SEO","Sun Apr 05 2026 03:16:48 GMT+0000 (Coordinated Universal Time)",{"categories":94},[95,99,103,107,109,113,116,119,123,127,130,133,137,140,143,146,149,153],{"category":96,"slug":97,"count":98},"Actualités SEO","actualites-seo",162,{"category":100,"slug":101,"count":102},"Migration","migration",18,{"category":104,"slug":105,"count":106},"Rendering","rendering",9,{"category":10,"slug":21,"count":108},8,{"category":110,"slug":111,"count":112},"Crawl","crawl",7,{"category":114,"slug":115,"count":112},"SEO Technique","seo-technique",{"category":117,"slug":118,"count":112},"Meta Tags","meta-tags",{"category":120,"slug":121,"count":122},"Architecture","architecture",6,{"category":124,"slug":125,"count":126},"JavaScript SEO","javascript-seo",5,{"category":128,"slug":129,"count":126},"Monitoring","monitoring",{"category":131,"slug":132,"count":126},"Structured Data","structured-data",{"category":134,"slug":135,"count":136},"Refonte","refonte",4,{"category":138,"slug":139,"count":136},"Redirections","redirections",{"category":141,"slug":142,"count":136},"Outils","outils",{"category":144,"slug":145,"count":136},"E-commerce","e-commerce",{"category":147,"slug":148,"count":136},"Avancé","avance",{"category":150,"slug":151,"count":152},"Contenu","contenu",3,{"category":154,"slug":155,"count":152},"IA & SEO","ia-seo"]