[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fpa5fCUd831fiOPjhB5ns0EzdR7mT-2IMKdDp0prNXwY":3,"$f85W0bPGxHSpxCGQYUC5xj4VjdiKK3tpHh77A7rz36A4":25},{"_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":23,"updatedAt":24},"69cfe4a2082ac50f03c954a1","lighthouse-ci-surveiller-la-performance-en-continu",0,"Equipe Seogard","Un déploiement anodin un mardi après-midi — une dépendance npm mise à jour, un composant hero refactoré — et le LCP de vos pages catégories passe de 1.8s à 4.2s. Personne ne s'en aperçoit pendant trois semaines, jusqu'à ce que le trafic organique décroche de 18%. Lighthouse CI existe précisément pour empêcher ce scénario.\n\n## Ce que Lighthouse CI fait (et ne fait pas)\n\nLighthouse CI (LHCI) est un wrapper autour de Lighthouse conçu pour s'intégrer dans les pipelines CI/CD. Il exécute des audits Lighthouse de façon automatisée à chaque commit, pull request ou déploiement, puis compare les résultats à des seuils prédéfinis ou à des runs historiques.\n\n### La différence avec un audit Lighthouse ponctuel\n\nUn audit manuel dans Chrome DevTools ou via PageSpeed Insights vous donne un instantané. C'est utile pour diagnostiquer, mais ça ne prévient rien. Vous lancez Lighthouse quand vous y pensez — c'est-à-dire après que le problème est visible.\n\nLHCI inverse la logique : chaque changement de code est audité automatiquement. Si le score performance descend sous un seuil, la CI échoue. Le développeur est alerté avant le merge, pas trois sprints plus tard quand un SEO constate la chute de trafic dans Search Console.\n\nCe que LHCI ne fait pas : il n'audite pas vos pages en conditions réelles d'utilisateur (c'est le rôle du CrUX / RUM). Il simule un environnement contrôlé (émulation mobile Moto G Power, throttling réseau). Les scores sont donc synthétiques — utiles pour détecter des régressions relatives entre deux déploiements, pas pour mesurer l'expérience terrain exacte.\n\n### Architecture de LHCI\n\nTrois composants principaux :\n\n- **LHCI CLI** : l'outil en ligne de commande qui collecte les audits, les compare aux assertions, et les upload.\n- **LHCI Server** : une application Node.js avec une base SQLite/PostgreSQL qui stocke l'historique des runs et fournit un dashboard de comparaison.\n- **Assertions** : un système de règles qui transforme les résultats Lighthouse en pass/fail dans votre pipeline.\n\nVous pouvez utiliser LHCI sans le serveur (en comparant uniquement aux assertions statiques), mais le serveur est ce qui rend le monitoring réellement continu — il permet de visualiser les tendances sur des semaines et de comparer les résultats entre branches.\n\n## Installation et configuration pas à pas\n\n### Prérequis\n\nLHCI tourne sur Node.js 16+. Il a besoin de Chrome/Chromium — la plupart des images CI Docker incluent déjà une version headless.\n\n```bash\n# Installation globale (CI runner)\nnpm install -g @lhci/cli@0.14.x\n\n# Ou en devDependency dans votre projet\nnpm install --save-dev @lhci/cli@0.14.x\n\n# Vérifier l'installation\nlhci --version\n\n# Si Chrome headless n'est pas disponible sur votre runner\n# (ex: image Alpine minimale), installez Puppeteer avec Chromium bundlé\nnpm install puppeteer\n```\n\n### Le fichier lighthouserc.js\n\nToute la configuration LHCI passe par un fichier `lighthouserc.js` (ou `.lighthouserc.json`, `.lighthouserc.yml`) à la racine du projet. Voici une configuration réaliste pour un e-commerce Next.js avec 12 000 pages :\n\n```javascript\n// lighthouserc.js\nmodule.exports = {\n  ci: {\n    collect: {\n      // URLs critiques à auditer — pas toutes les 12K pages,\n      // mais un échantillon représentatif par template\n      url: [\n        'http://localhost:3000/',                          // Homepage\n        'http://localhost:3000/c/chaussures-running',      // Page catégorie (listing)\n        'http://localhost:3000/p/nike-pegasus-41-noir',     // Page produit (PDP)\n        'http://localhost:3000/blog/guide-pronation',       // Article éditorial\n        'http://localhost:3000/recherche?q=trail',          // Page recherche interne\n      ],\n      // Nombre de runs par URL — 3 minimum pour lisser la variance\n      numberOfRuns: 5,\n      // Démarrer le serveur local avant les audits\n      startServerCommand: 'npm run start',\n      startServerReadyPattern: 'ready on',\n      startServerReadyTimeout: 30000,\n      settings: {\n        // Émulation identique à PageSpeed Insights mobile\n        preset: 'desktop', // ou 'perf' pour mobile\n        // Throttling personnalisé si nécessaire\n        throttling: {\n          cpuSlowdownMultiplier: 4,\n          downloadThroughputKbps: 1600,\n          uploadThroughputKbps: 750,\n          rttMs: 150,\n        },\n        // Skip les catégories non pertinentes pour gagner du temps\n        onlyCategories: ['performance', 'accessibility', 'seo'],\n        // Chrome flags pour environnement CI headless\n        chromeFlags: ['--no-sandbox', '--headless', '--disable-gpu'],\n      },\n    },\n    assert: {\n      assertions: {\n        // Score global performance : warning sous 85, erreur sous 70\n        'categories:performance': ['error', { minScore: 0.7 }],\n        'categories:seo': ['error', { minScore: 0.9 }],\n        'categories:accessibility': ['warn', { minScore: 0.8 }],\n\n        // Métriques Core Web Vitals individuelles\n        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],\n        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],\n        'total-blocking-time': ['error', { maxNumericValue: 300 }],\n\n        // Détection de régressions spécifiques\n        'first-contentful-paint': ['warn', { maxNumericValue: 1800 }],\n        'speed-index': ['warn', { maxNumericValue: 3400 }],\n\n        // Assertions SEO critiques\n        'meta-description': 'error',\n        'document-title': 'error',\n        'http-status-code': 'error',\n        'is-crawlable': 'error',\n        'canonical': 'error',\n        'hreflang': 'warn',\n      },\n    },\n    upload: {\n      // Option 1 : LHCI Server auto-hébergé\n      target: 'lhci',\n      serverBaseUrl: 'https://lhci.votredomaine.com',\n      token: process.env.LHCI_BUILD_TOKEN,\n\n      // Option 2 : temporary-public-storage (gratuit, données publiques, 7j de rétention)\n      // target: 'temporary-public-storage',\n    },\n  },\n};\n```\n\nQuelques points importants sur cette configuration :\n\nLe choix des URLs est stratégique. Vous n'allez pas auditer 12 000 pages à chaque commit — un pipeline CI qui prend 45 minutes ne sera pas respecté par les développeurs. Sélectionnez une URL par template critique. La homepage, une page catégorie avec beaucoup de produits, une page produit, un article — c'est suffisant pour détecter 90% des régressions de performance liées au code.\n\nLe `numberOfRuns: 5` est un compromis entre fiabilité statistique et temps d'exécution. Lighthouse est notoirement variable d'un run à l'autre (±5-10% sur le score performance). LHCI prend la médiane des runs, ce qui lisse les outliers. En dessous de 3, vous aurez des faux positifs réguliers.\n\n### Assertions : la logique de seuils\n\nLe système d'assertions est le cœur de LHCI. Chaque assertion accepte deux niveaux :\n\n- `error` : la CI échoue, le merge est bloqué\n- `warn` : un avertissement est loggé mais la CI passe\n\nLa stratégie recommandée : mettez en `error` les métriques qui ont un impact SEO direct mesurable (LCP, CLS, TBT comme proxy d'INP, et les audits SEO on-page). Mettez en `warn` les métriques secondaires ou les catégories où vos seuils sont encore en cours de calibration.\n\nPour aller plus loin sur la stratégie de seuils et de fréquence d'alerting, consultez [cet article sur les seuils et fréquences d'alertes SEO](/blog/alertes-seo-quels-seuils-et-quelle-frequence).\n\n## Intégration dans les pipelines CI/CD\n\n### GitHub Actions\n\nVoici un workflow GitHub Actions complet, testé en production :\n\n```yaml\n# .github/workflows/lighthouse-ci.yml\nname: Lighthouse CI\non:\n  pull_request:\n    branches: [main, staging]\n  push:\n    branches: [main]\n\njobs:\n  lighthouse:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: '20'\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Build\n        run: npm run build\n        env:\n          NODE_ENV: production\n\n      - name: Run Lighthouse CI\n        run: |\n          npm install -g @lhci/cli@0.14.x\n          lhci autorun\n        env:\n          LHCI_BUILD_TOKEN: ${{ secrets.LHCI_BUILD_TOKEN }}\n          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}\n\n      # Optionnel : upload les rapports HTML comme artifacts\n      - name: Upload Lighthouse reports\n        if: always()\n        uses: actions/upload-artifact@v4\n        with:\n          name: lighthouse-reports\n          path: .lighthouseci/\n          retention-days: 14\n```\n\nLa commande `lhci autorun` exécute séquentiellement : `collect` → `assert` → `upload`. Si les assertions échouent, le process exit avec un code non-zéro, ce qui fait échouer le step GitHub Actions et bloque le merge si vous avez activé les branch protection rules.\n\n### GitLab CI\n\n```yaml\n# .gitlab-ci.yml\nlighthouse:\n  stage: test\n  image: node:20-bullseye\n  before_script:\n    - apt-get update && apt-get install -y chromium\n    - npm ci\n    - npm install -g @lhci/cli@0.14.x\n  script:\n    - npm run build\n    - lhci autorun\n  variables:\n    CHROME_PATH: /usr/bin/chromium\n    LHCI_BUILD_TOKEN: $LHCI_BUILD_TOKEN\n  artifacts:\n    paths:\n      - .lighthouseci/\n    expire_in: 2 weeks\n  only:\n    - merge_requests\n    - main\n```\n\nLe point de friction le plus fréquent en CI : Chrome/Chromium qui ne se lance pas dans le conteneur. L'image `node:20-bullseye` combinée avec `apt-get install chromium` et le flag `--no-sandbox` dans la config Lighthouse résout 95% des cas. Si vous utilisez Alpine, passez à Debian — les dépendances de Chrome sur Alpine sont un enfer de configuration.\n\n### Parallélisation et temps d'exécution\n\nAvec 5 URLs et 5 runs chacune, LHCI exécute 25 audits Lighthouse séquentiellement. Chaque audit prend ~15-30 secondes selon la complexité de la page. Comptez 6-12 minutes pour le job complet.\n\nPour réduire ce temps sur des projets à pipeline critique :\n\n- Réduisez à 3 runs (acceptable si votre CI est stable)\n- Limitez à `onlyCategories: ['performance']` si les audits SEO et a11y sont couverts ailleurs\n- Exécutez LHCI uniquement sur les PRs qui touchent des fichiers frontend (`paths` filter dans le workflow)\n\nC'est un trade-off entre couverture et feedback loop. Un pipeline de 12 minutes qui tourne à chaque commit est acceptable. Un pipeline de 30 minutes qui audite 20 URLs avec 7 runs ne sera bientôt plus exécuté que sur `main`.\n\n## Déployer le LHCI Server pour l'historique\n\nLe temporary public storage fourni par Google est pratique pour démarrer, mais les données sont publiques et supprimées après 7 jours. Pour un monitoring continu réel, vous avez besoin du LHCI Server.\n\n### Docker Compose\n\n```yaml\n# docker-compose.lhci.yml\nversion: '3.8'\nservices:\n  lhci-server:\n    image: patrickhulce/lhci-server:0.14.0\n    ports:\n      - '9001:9001'\n    volumes:\n      - lhci-data:/data\n    environment:\n      - LHCI_STORAGE__SQL_DIALECT=sqlite\n      - LHCI_STORAGE__SQL_DATABASE_PATH=/data/lhci.db\n      # Pour PostgreSQL en production :\n      # - LHCI_STORAGE__SQL_DIALECT=postgres\n      # - LHCI_STORAGE__SQL_CONNECTION_URL=postgresql://user:pass@db:5432/lhci\n    restart: unless-stopped\n\nvolumes:\n  lhci-data:\n```\n\nAprès le déploiement, créez un projet et récupérez le build token :\n\n```bash\n# Création du projet sur le LHCI Server\nlhci wizard --wizard=new-project --serverBaseUrl=https://lhci.votredomaine.com\n# Output :\n# Created project \"mon-ecommerce\" (id: xxxxx)\n# Build token: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n# Admin token: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n# Use the build token in your CI config\n```\n\nLe dashboard LHCI Server est minimaliste mais fonctionnel. Il affiche les courbes de chaque métrique par URL au fil des builds, avec un diff visuel entre deux runs. C'est suffisant pour repérer une dégradation progressive du Speed Index sur trois semaines — le type de régression invisible dans un audit ponctuel.\n\n### Limites du LHCI Server\n\nLe dashboard LHCI est centré sur la performance Lighthouse. Il ne corrèle pas avec vos données de trafic organique, ne surveille pas les changements de contenu (meta descriptions supprimées, canonicals cassées en production), et n'alerte pas en temps réel.\n\nC'est un outil de gate CI, pas un outil de monitoring SEO complet. Pour la détection continue de [régressions SEO en production](/blog/regressions-seo-les-10-types-les-plus-frequents) — metas disparues, erreurs de SSR, backlinks perdus — un outil de monitoring dédié comme Seogard complète LHCI en couvrant ce que le pipeline CI ne voit pas : les problèmes qui surviennent après le déploiement, côté infrastructure ou côté données.\n\n## Scénario réel : migration SSR d'un e-commerce\n\nPrenons un cas concret. Un e-commerce spécialisé outdoor — 15 000 pages produit, 800 pages catégories, 200 articles de blog — migre de Create React App (CSR pur) vers Next.js avec SSR. L'objectif : améliorer l'indexabilité et les Core Web Vitals.\n\n### Avant la migration\n\nLes scores Lighthouse sur les pages clés (mesurés avec Chrome DevTools en émulation mobile) :\n\n- **Homepage** : Performance 42, LCP 5.8s, CLS 0.32\n- **Page catégorie** : Performance 28, LCP 7.1s (hydratation tardive du listing produit)\n- **Page produit** : Performance 35, LCP 6.4s (image hero chargée côté client)\n\nLe [rendu CSR causait des divergences majeures](/blog/comparer-ssr-et-csr-detecter-les-divergences-invisibles) entre ce que Googlebot voyait et ce que les utilisateurs expérimentaient. Les pages étaient techniquement indexées, mais avec un contenu partiel — les données structurées Product étaient injectées côté client et irrégulièrement captées par le crawler.\n\n### Configuration LHCI pendant la migration\n\nL'équipe configure LHCI dès le premier sprint de migration, avec des seuils progressifs :\n\n**Sprint 1-3 (fondations Next.js)** : assertions en `warn` uniquement, seuils larges. L'objectif est de collecter une baseline.\n\n**Sprint 4-6 (optimisation SSR)** : passage en `error` pour le LCP (\u003C 3500ms), le CLS (\u003C 0.15), et les audits SEO Lighthouse.\n\n**Sprint 7+ (fine-tuning)** : seuils resserrés vers les objectifs finaux — LCP \u003C 2500ms, CLS \u003C 0.1, TBT \u003C 300ms.\n\n### Le bug détecté par LHCI\n\nAu sprint 5, un développeur refactore le composant de lazy loading des images produit. Le nouveau code utilise `loading=\"lazy\"` sur l'image hero above the fold — une erreur classique qui empêche le navigateur de prioriser le LCP element.\n\n```typescript\n// ❌ Code problématique détecté par LHCI\n// Le composant ProductImage appliquait loading=\"lazy\" à toutes les images\nconst ProductImage: React.FC\u003CProductImageProps> = ({ src, alt, priority }) => {\n  return (\n    \u003CImage\n      src={src}\n      alt={alt}\n      width={800}\n      height={600}\n      loading=\"lazy\"     // ← Appliqué même à l'image hero (priority=true)\n      decoding=\"async\"\n    />\n  );\n};\n\n// ✅ Correction après l'alerte LHCI\nconst ProductImage: React.FC\u003CProductImageProps> = ({ src, alt, priority }) => {\n  return (\n    \u003CImage\n      src={src}\n      alt={alt}\n      width={800}\n      height={600}\n      loading={priority ? 'eager' : 'lazy'}\n      decoding={priority ? 'sync' : 'async'}\n      // Next.js Image avec priority={true} gère automatiquement le preload\n      priority={priority}\n      // fetchPriority pour les navigateurs qui le supportent\n      fetchPriority={priority ? 'high' : 'auto'}\n    />\n  );\n};\n```\n\nLHCI a bloqué le merge : le LCP de la page produit est passé de 2.1s à 3.8s entre la branche `main` et la PR. Le développeur a corrigé en 10 minutes. Sans LHCI, ce bug aurait été déployé un vendredi (comme c'est [malheureusement souvent le cas](/blog/deploiement-vendredi-soir-comment-eviter-la-catastrophe-seo)) et serait resté en production jusqu'au prochain audit manuel.\n\n### Résultats post-migration\n\nAprès 8 sprints et ~200 builds LHCI :\n\n- **LCP moyen pages produit** : 6.4s → 1.9s\n- **CLS moyen pages catégories** : 0.32 → 0.04\n- **Score performance Lighthouse** : 28-42 → 78-91 selon le template\n- **Pages indexées dans Search Console** : +23% en 6 semaines (les pages auparavant rendues partiellement sont maintenant crawlées avec leur contenu complet)\n\nLe trafic organique a augmenté de 31% sur les 3 mois suivant la fin de la migration — mais il est impossible d'attribuer ce gain uniquement aux Core Web Vitals. L'amélioration du SSR a aussi résolu des problèmes d'indexation du contenu. C'est la réalité du SEO technique : les optimisations s'empilent et leurs effets sont corrélés.\n\n## Stratégies avancées de configuration\n\n### Assertions budgétaires avec performance budgets\n\nAu-delà des scores Lighthouse, LHCI supporte les [performance budgets](https://web.dev/articles/performance-budgets-101) — des limites sur le poids des ressources :\n\n```javascript\n// lighthouserc.js — section assert\nassert: {\n  assertions: {\n    'resource-summary:script:size': ['error', { maxNumericValue: 350000 }],   // JS \u003C 350KB\n    'resource-summary:image:size': ['warn', { maxNumericValue: 500000 }],     // Images \u003C 500KB\n    'resource-summary:third-party:count': ['warn', { maxNumericValue: 8 }],   // Max 8 third-parties\n    'resource-summary:total:size': ['error', { maxNumericValue: 1500000 }],   // Total \u003C 1.5MB\n    'unused-javascript': ['warn', { maxLength: 2 }],                          // Max 2 scripts inutilisés\n  },\n},\n```\n\nCes budgets détectent une catégorie de régressions que les scores Lighthouse ne montrent pas toujours : un bundle JS qui grossit de 20KB à chaque sprint. Le score reste à 85, mais le bundle passe de 200KB à 450KB en 6 mois. Un jour, le score décroche brutalement — et il est trop tard pour identifier quel commit a ajouté les 250KB.\n\n### Audits conditionnels par type de page\n\nSi votre site a des templates aux caractéristiques très différentes (pages produit avec beaucoup d'images vs. articles de blog textuels), des seuils uniques sont trop permissifs pour certains templates et trop stricts pour d'autres.\n\nLa solution : plusieurs configurations LHCI avec des overrides par URL pattern.\n\n```javascript\n// lighthouserc.js\nmodule.exports = {\n  ci: {\n    collect: {\n      url: [\n        'http://localhost:3000/',\n        'http://localhost:3000/c/chaussures-running',\n        'http://localhost:3000/p/nike-pegasus-41-noir',\n        'http://localhost:3000/blog/guide-pronation',\n      ],\n      numberOfRuns: 5,\n      startServerCommand: 'npm run start',\n    },\n    assert: {\n      assertions: {\n        // Seuils par défaut\n        'categories:performance': ['error', { minScore: 0.7 }],\n        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],\n      },\n      assertMatrix: [\n        {\n          // Pages produit : LCP plus strict (image hero optimisée attendue)\n          matchingUrlPattern: '.*/p/.*',\n          assertions: {\n            'largest-contentful-paint': ['error', { maxNumericValue: 2200 }],\n            'cumulative-layout-shift': ['error', { maxNumericValue: 0.05 }],\n          },\n        },\n        {\n          // Pages catégorie : tolérance plus large (listings dynamiques)\n          matchingUrlPattern: '.*/c/.*',\n          assertions: {\n            'largest-contentful-paint': ['error', { maxNumericValue: 2800 }],\n            'total-blocking-time': ['error', { maxNumericValue: 400 }],\n          },\n        },\n        {\n          // Blog : performance stricte (pages légères)\n          matchingUrlPattern: '.*/blog/.*',\n          assertions: {\n            'categories:performance': ['error', { minScore: 0.9 }],\n            'largest-contentful-paint': ['error', { maxNumericValue: 1800 }],\n          },\n        },\n      ],\n    },\n    upload: {\n      target: 'lhci',\n      serverBaseUrl: 'https://lhci.votredomaine.com',\n      token: process.env.LHCI_BUILD_TOKEN,\n    },\n  },\n};\n```\n\nL'`assertMatrix` est une fonctionnalité puissante mais peu documentée. Elle permet de définir des contrats de performance différents par template — exactement ce dont un site de 15K+ pages a besoin.\n\n### Intégration avec Screaming Frog et Search Console\n\nLHCI couvre le monitoring pré-déploiement. Mais pour valider l'impact en production, croisez les données :\n\n**Search Console** : le rapport Core Web Vitals (sous \"Expérience\") montre les données CrUX réelles. Après une amélioration détectée par LHCI en synthétique, vérifiez que les métriques terrain suivent dans les 28 jours suivants. Si LHCI montre un LCP à 2.0s mais CrUX reste à 3.5s, votre environnement CI ne reproduit pas les conditions réelles (serveur plus rapide, CDN absent, etc.). Consultez les [rapports Search Console souvent négligés](/blog/google-search-console-les-rapports-que-vous-ignorez) pour exploiter pleinement ces données.\n\n**Screaming Frog** : pour auditer les URLs que LHCI ne couvre pas. Un crawl Screaming Frog de vos 15K pages avec l'intégration PageSpeed Insights activée prend ~4-6 heures (rate limited à ~2 requêtes/seconde par l'API PSI). Faites-le mensuellement pour identifier les templates ou pages spécifiques où la performance dévie de votre échantillon LHCI.\n\n**Chrome DevTools** : pour le diagnostic post-régression. Quand LHCI détecte un LCP dégradé, utilisez le panel Performance de Chrome DevTools pour tracer l'origine exacte — est-ce le serveur (TTFB), le rendering, ou un script tiers ? Les [techniques avancées de Chrome DevTools pour le SEO](/blog/chrome-devtools-pour-le-seo-astuces-avancees) couvrent ces workflows en détail.\n\n## Les pièges courants et comment les éviter\n\n### Variance des résultats en CI\n\nLe piège numéro un : des tests flaky qui échouent aléatoirement. Un runner CI partagé avec d'autres jobs, un réseau instable, un garbage collection Chrome mal tombé — et votre LCP varie de 800ms entre deux runs identiques.\n\nSolutions concrètes :\n\n- **5 runs minimum** avec prise de la médiane (pas la moyenne)\n- **Runner dédié** ou at minimum un runner avec des ressources CPU/RAM prévisibles\n- **Seuils avec marge** : si votre objectif réel est un LCP de 2.5s, mettez le seuil LHCI à 2.8s ou 3.0s pour absorber la variance synthétique\n- **`--chrome-flags=\"--deterministic-mode\"`** : réduit la variance mais n'est pas supporté dans toutes les versions de Chrome\n\n### Tester contre localhost vs. staging\n\nLHCI teste typiquement un serveur local (`startServerCommand`). Cela signifie : pas de CDN, pas de latence réseau réelle, pas de charge concurrente. Votre LCP local à 1.2s deviendra 2.4s en production derrière un CDN mal configuré ou avec des tiers qui ajoutent 500ms.\n\nPour les projets matures, ajoutez un second job LHCI qui teste contre l'environnement de staging après déploiement :\n\n```javascript\n// lighthouserc.staging.js\nmodule.exports = {\n  ci: {\n    collect: {\n      url: [\n        'https://staging.votredomaine.com/',\n        'https://staging.votredomaine.com/c/chaussures-running',\n        'https://staging.votredomaine.com/p/nike-pegasus-41-noir',\n      ],\n      numberOfRuns: 3,\n      // Pas de startServerCommand — on teste un environnement distant\n    },\n    // ... assertions et upload identiques\n  },\n};\n```\n\nC'est plus réaliste, mais plus lent et dépendant de l'infrastructure staging. Le meilleur setup combine les deux : LHCI local en gate sur les PR (feedback rapide), LHCI staging post-deploy sur `main` (validation réaliste).\n\n### Ne pas auditer les bonnes pages\n\nSi vous n'auditez que la homepage, vous manquerez la régression du composant de listing produit qui affecte 800 pages catégories. Inversement, auditer 50 URLs différentes ralentit le pipeline au point de devenir inutile.\n\nLa règle : **une URL par template, pondérée par le trafic**. Identifiez dans Search Console vos 4-5 templates qui génèrent 80% du trafic organique. Choisissez l'URL la plus représentative de chaque template (ni la plus légère, ni la plus lourde — la médiane).\n\n## Complémentarité avec le monitoring continu\n\nLHCI est un garde-fou pré-déploiement. Il répond à la question : \"est-ce que ce changement de code dégrade la performance ?\" Mais il ne répond pas à : \"est-ce que la performance en production est stable cette semaine ?\"\n\nLes régressions qui échappent à LHCI :\n\n- Un tiers (analytics, A/B testing, consent manager) qui ralentit après une mise à jour côté provider\n- Un CDN qui expire un cache et force des recalculs serveur\n- Une base de données qui ralentit sous la charge et dégrade le TTFB\n- Un changement de configuration Nginx/Cloudflare fait par l'ops sans passer par le pipeline CI\n\nCes régressions surviennent en production, sans changement de code. [Les audits ponctuels ne les détectent pas](/blog/monitoring-seo-pourquoi-les-audits-ponctuels-ne-suffisent-plus) — seul un monitoring continu en production le peut.\n\nL'approche robuste combine trois couches : LHCI dans le CI/CD pour les régressions code, des données RUM (Real User Monitoring) pour les performances terrain, et un [pipeline d'automatisation SEO dans le CI/CD](/blog/automatiser-les-checks-seo-dans-le-ci-cd) qui couvre les aspects non-performance (metas, canonicals, robots, structured data).\n\nLHCI est un outil de développeur, pas un outil de monitoring SEO. Il fait très bien son job — bloquer les régressions de performance au niveau du code — mais il ne remplace ni la Search Console, ni Screaming Frog, ni un monitoring de production continu. Utilisez-le comme première ligne de défense, et complétez avec des outils qui surveillent ce qui se passe réellement sur vos pages en production, 24/7.\n```","https://seogard.io/blog/lighthouse-ci-surveiller-la-performance-en-continu","Outils","2026-04-03T16:02:42.766Z","2026-04-03","Configurer Lighthouse CI dans votre pipeline pour détecter les régressions de performance avant qu'elles n'impactent le SEO. Guide technique complet.","\u003Cp>Un déploiement anodin un mardi après-midi — une dépendance npm mise à jour, un composant hero refactoré — et le LCP de vos pages catégories passe de 1.8s à 4.2s. Personne ne s'en aperçoit pendant trois semaines, jusqu'à ce que le trafic organique décroche de 18%. Lighthouse CI existe précisément pour empêcher ce scénario.\u003C/p>\n\u003Ch2>Ce que Lighthouse CI fait (et ne fait pas)\u003C/h2>\n\u003Cp>Lighthouse CI (LHCI) est un wrapper autour de Lighthouse conçu pour s'intégrer dans les pipelines CI/CD. Il exécute des audits Lighthouse de façon automatisée à chaque commit, pull request ou déploiement, puis compare les résultats à des seuils prédéfinis ou à des runs historiques.\u003C/p>\n\u003Ch3>La différence avec un audit Lighthouse ponctuel\u003C/h3>\n\u003Cp>Un audit manuel dans Chrome DevTools ou via PageSpeed Insights vous donne un instantané. C'est utile pour diagnostiquer, mais ça ne prévient rien. Vous lancez Lighthouse quand vous y pensez — c'est-à-dire après que le problème est visible.\u003C/p>\n\u003Cp>LHCI inverse la logique : chaque changement de code est audité automatiquement. Si le score performance descend sous un seuil, la CI échoue. Le développeur est alerté avant le merge, pas trois sprints plus tard quand un SEO constate la chute de trafic dans Search Console.\u003C/p>\n\u003Cp>Ce que LHCI ne fait pas : il n'audite pas vos pages en conditions réelles d'utilisateur (c'est le rôle du CrUX / RUM). Il simule un environnement contrôlé (émulation mobile Moto G Power, throttling réseau). Les scores sont donc synthétiques — utiles pour détecter des régressions relatives entre deux déploiements, pas pour mesurer l'expérience terrain exacte.\u003C/p>\n\u003Ch3>Architecture de LHCI\u003C/h3>\n\u003Cp>Trois composants principaux :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>LHCI CLI\u003C/strong> : l'outil en ligne de commande qui collecte les audits, les compare aux assertions, et les upload.\u003C/li>\n\u003Cli>\u003Cstrong>LHCI Server\u003C/strong> : une application Node.js avec une base SQLite/PostgreSQL qui stocke l'historique des runs et fournit un dashboard de comparaison.\u003C/li>\n\u003Cli>\u003Cstrong>Assertions\u003C/strong> : un système de règles qui transforme les résultats Lighthouse en pass/fail dans votre pipeline.\u003C/li>\n\u003C/ul>\n\u003Cp>Vous pouvez utiliser LHCI sans le serveur (en comparant uniquement aux assertions statiques), mais le serveur est ce qui rend le monitoring réellement continu — il permet de visualiser les tendances sur des semaines et de comparer les résultats entre branches.\u003C/p>\n\u003Ch2>Installation et configuration pas à pas\u003C/h2>\n\u003Ch3>Prérequis\u003C/h3>\n\u003Cp>LHCI tourne sur Node.js 16+. Il a besoin de Chrome/Chromium — la plupart des images CI Docker incluent déjà une version headless.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Installation globale (CI runner)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">npm\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> install\u003C/span>\u003Cspan style=\"color:#79B8FF\"> -g\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> @lhci/cli@0.14.x\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Ou en devDependency dans votre projet\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">npm\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> install\u003C/span>\u003Cspan style=\"color:#79B8FF\"> --save-dev\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> @lhci/cli@0.14.x\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Vérifier l'installation\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">lhci\u003C/span>\u003Cspan style=\"color:#79B8FF\"> --version\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Si Chrome headless n'est pas disponible sur votre runner\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># (ex: image Alpine minimale), installez Puppeteer avec Chromium bundlé\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">npm\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> install\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> puppeteer\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Ch3>Le fichier lighthouserc.js\u003C/h3>\n\u003Cp>Toute la configuration LHCI passe par un fichier \u003Ccode>lighthouserc.js\u003C/code> (ou \u003Ccode>.lighthouserc.json\u003C/code>, \u003Ccode>.lighthouserc.yml\u003C/code>) à la racine du projet. Voici une configuration réaliste pour un e-commerce Next.js avec 12 000 pages :\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.js\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">module\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#79B8FF\">exports\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  ci: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    collect: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      // URLs critiques à auditer — pas toutes les 12K pages,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      // mais un échantillon représentatif par template\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      url: [\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'http://localhost:3000/'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,                          \u003C/span>\u003Cspan style=\"color:#6A737D\">// Homepage\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'http://localhost:3000/c/chaussures-running'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,      \u003C/span>\u003Cspan style=\"color:#6A737D\">// Page catégorie (listing)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'http://localhost:3000/p/nike-pegasus-41-noir'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,     \u003C/span>\u003Cspan style=\"color:#6A737D\">// Page produit (PDP)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'http://localhost:3000/blog/guide-pronation'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,       \u003C/span>\u003Cspan style=\"color:#6A737D\">// Article éditorial\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'http://localhost:3000/recherche?q=trail'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,          \u003C/span>\u003Cspan style=\"color:#6A737D\">// Page recherche interne\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      ],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      // Nombre de runs par URL — 3 minimum pour lisser la variance\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      numberOfRuns: \u003C/span>\u003Cspan style=\"color:#79B8FF\">5\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      // Démarrer le serveur local avant les audits\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      startServerCommand: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'npm run start'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      startServerReadyPattern: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'ready on'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      startServerReadyTimeout: \u003C/span>\u003Cspan style=\"color:#79B8FF\">30000\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      settings: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        // Émulation identique à PageSpeed Insights mobile\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        preset: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'desktop'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#6A737D\">// ou 'perf' pour mobile\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        // Throttling personnalisé si nécessaire\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        throttling: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          cpuSlowdownMultiplier: \u003C/span>\u003Cspan style=\"color:#79B8FF\">4\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          downloadThroughputKbps: \u003C/span>\u003Cspan style=\"color:#79B8FF\">1600\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          uploadThroughputKbps: \u003C/span>\u003Cspan style=\"color:#79B8FF\">750\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          rttMs: \u003C/span>\u003Cspan style=\"color:#79B8FF\">150\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:#6A737D\">        // Skip les catégories non pertinentes pour gagner du temps\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        onlyCategories: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'performance'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'accessibility'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'seo'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        // Chrome flags pour environnement CI headless\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        chromeFlags: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'--no-sandbox'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'--headless'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'--disable-gpu'\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\">    },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    assert: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      assertions: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        // Score global performance : warning sous 85, erreur sous 70\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'categories:performance'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { minScore: \u003C/span>\u003Cspan style=\"color:#79B8FF\">0.7\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'categories:seo'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { minScore: \u003C/span>\u003Cspan style=\"color:#79B8FF\">0.9\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'categories:accessibility'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'warn'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { minScore: \u003C/span>\u003Cspan style=\"color:#79B8FF\">0.8\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        // Métriques Core Web Vitals individuelles\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'largest-contentful-paint'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">2500\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'cumulative-layout-shift'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">0.1\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'total-blocking-time'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">300\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        // Détection de régressions spécifiques\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'first-contentful-paint'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'warn'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">1800\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'speed-index'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'warn'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">3400\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        // Assertions SEO critiques\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'meta-description'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'document-title'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'http-status-code'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'is-crawlable'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'canonical'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'hreflang'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'warn'\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\">    },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    upload: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      // Option 1 : LHCI Server auto-hébergé\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      target: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'lhci'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      serverBaseUrl: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'https://lhci.votredomaine.com'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      token: process.env.\u003C/span>\u003Cspan style=\"color:#79B8FF\">LHCI_BUILD_TOKEN\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      // Option 2 : temporary-public-storage (gratuit, données publiques, 7j de rétention)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      // target: 'temporary-public-storage',\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\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\">};\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Quelques points importants sur cette configuration :\u003C/p>\n\u003Cp>Le choix des URLs est stratégique. Vous n'allez pas auditer 12 000 pages à chaque commit — un pipeline CI qui prend 45 minutes ne sera pas respecté par les développeurs. Sélectionnez une URL par template critique. La homepage, une page catégorie avec beaucoup de produits, une page produit, un article — c'est suffisant pour détecter 90% des régressions de performance liées au code.\u003C/p>\n\u003Cp>Le \u003Ccode>numberOfRuns: 5\u003C/code> est un compromis entre fiabilité statistique et temps d'exécution. Lighthouse est notoirement variable d'un run à l'autre (±5-10% sur le score performance). LHCI prend la médiane des runs, ce qui lisse les outliers. En dessous de 3, vous aurez des faux positifs réguliers.\u003C/p>\n\u003Ch3>Assertions : la logique de seuils\u003C/h3>\n\u003Cp>Le système d'assertions est le cœur de LHCI. Chaque assertion accepte deux niveaux :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Ccode>error\u003C/code> : la CI échoue, le merge est bloqué\u003C/li>\n\u003Cli>\u003Ccode>warn\u003C/code> : un avertissement est loggé mais la CI passe\u003C/li>\n\u003C/ul>\n\u003Cp>La stratégie recommandée : mettez en \u003Ccode>error\u003C/code> les métriques qui ont un impact SEO direct mesurable (LCP, CLS, TBT comme proxy d'INP, et les audits SEO on-page). Mettez en \u003Ccode>warn\u003C/code> les métriques secondaires ou les catégories où vos seuils sont encore en cours de calibration.\u003C/p>\n\u003Cp>Pour aller plus loin sur la stratégie de seuils et de fréquence d'alerting, consultez \u003Ca href=\"/blog/alertes-seo-quels-seuils-et-quelle-frequence\">cet article sur les seuils et fréquences d'alertes SEO\u003C/a>.\u003C/p>\n\u003Ch2>Intégration dans les pipelines CI/CD\u003C/h2>\n\u003Ch3>GitHub Actions\u003C/h3>\n\u003Cp>Voici un workflow GitHub Actions complet, testé en production :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># .github/workflows/lighthouse-ci.yml\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">Lighthouse CI\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">on\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  pull_request\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    branches\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">main\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#9ECBFF\">staging\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  push\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    branches\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">main\u003C/span>\u003Cspan style=\"color:#E1E4E8\">]\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">jobs\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  lighthouse\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    runs-on\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">ubuntu-latest\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    steps\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#85E89D\">uses\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">actions/checkout@v4\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#85E89D\">uses\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">actions/setup-node@v4\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        with\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">          node-version\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'20'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">          cache\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'npm'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#85E89D\">name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">Install dependencies\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        run\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">npm ci\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#85E89D\">name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">Build\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        run\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">npm run build\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        env\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">          NODE_ENV\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">production\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#85E89D\">name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">Run Lighthouse CI\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        run\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#F97583\">|\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          npm install -g @lhci/cli@0.14.x\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">          lhci autorun\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        env\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">          LHCI_BUILD_TOKEN\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">${{ secrets.LHCI_BUILD_TOKEN }}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">          LHCI_GITHUB_APP_TOKEN\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">${{ secrets.LHCI_GITHUB_APP_TOKEN }}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      # Optionnel : upload les rapports HTML comme artifacts\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#85E89D\">name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">Upload Lighthouse reports\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        if\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">always()\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        uses\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">actions/upload-artifact@v4\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">        with\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">          name\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">lighthouse-reports\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">          path\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">.lighthouseci/\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">          retention-days\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#79B8FF\">14\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>La commande \u003Ccode>lhci autorun\u003C/code> exécute séquentiellement : \u003Ccode>collect\u003C/code> → \u003Ccode>assert\u003C/code> → \u003Ccode>upload\u003C/code>. Si les assertions échouent, le process exit avec un code non-zéro, ce qui fait échouer le step GitHub Actions et bloque le merge si vous avez activé les branch protection rules.\u003C/p>\n\u003Ch3>GitLab CI\u003C/h3>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># .gitlab-ci.yml\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">lighthouse\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  stage\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">test\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  image\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">node:20-bullseye\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  before_script\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">apt-get update &#x26;&#x26; apt-get install -y chromium\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">npm ci\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">npm install -g @lhci/cli@0.14.x\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  script\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">npm run build\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">lhci autorun\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  variables\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    CHROME_PATH\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">/usr/bin/chromium\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    LHCI_BUILD_TOKEN\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">$LHCI_BUILD_TOKEN\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  artifacts\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    paths\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">.lighthouseci/\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    expire_in\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">2 weeks\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  only\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">merge_requests\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">main\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le point de friction le plus fréquent en CI : Chrome/Chromium qui ne se lance pas dans le conteneur. L'image \u003Ccode>node:20-bullseye\u003C/code> combinée avec \u003Ccode>apt-get install chromium\u003C/code> et le flag \u003Ccode>--no-sandbox\u003C/code> dans la config Lighthouse résout 95% des cas. Si vous utilisez Alpine, passez à Debian — les dépendances de Chrome sur Alpine sont un enfer de configuration.\u003C/p>\n\u003Ch3>Parallélisation et temps d'exécution\u003C/h3>\n\u003Cp>Avec 5 URLs et 5 runs chacune, LHCI exécute 25 audits Lighthouse séquentiellement. Chaque audit prend ~15-30 secondes selon la complexité de la page. Comptez 6-12 minutes pour le job complet.\u003C/p>\n\u003Cp>Pour réduire ce temps sur des projets à pipeline critique :\u003C/p>\n\u003Cul>\n\u003Cli>Réduisez à 3 runs (acceptable si votre CI est stable)\u003C/li>\n\u003Cli>Limitez à \u003Ccode>onlyCategories: ['performance']\u003C/code> si les audits SEO et a11y sont couverts ailleurs\u003C/li>\n\u003Cli>Exécutez LHCI uniquement sur les PRs qui touchent des fichiers frontend (\u003Ccode>paths\u003C/code> filter dans le workflow)\u003C/li>\n\u003C/ul>\n\u003Cp>C'est un trade-off entre couverture et feedback loop. Un pipeline de 12 minutes qui tourne à chaque commit est acceptable. Un pipeline de 30 minutes qui audite 20 URLs avec 7 runs ne sera bientôt plus exécuté que sur \u003Ccode>main\u003C/code>.\u003C/p>\n\u003Ch2>Déployer le LHCI Server pour l'historique\u003C/h2>\n\u003Cp>Le temporary public storage fourni par Google est pratique pour démarrer, mais les données sont publiques et supprimées après 7 jours. Pour un monitoring continu réel, vous avez besoin du LHCI Server.\u003C/p>\n\u003Ch3>Docker Compose\u003C/h3>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># docker-compose.lhci.yml\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">version\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'3.8'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">services\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  lhci-server\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    image\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">patrickhulce/lhci-server:0.14.0\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    ports\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'9001:9001'\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    volumes\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">lhci-data:/data\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    environment\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">LHCI_STORAGE__SQL_DIALECT=sqlite\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      - \u003C/span>\u003Cspan style=\"color:#9ECBFF\">LHCI_STORAGE__SQL_DATABASE_PATH=/data/lhci.db\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      # Pour PostgreSQL en production :\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      # - LHCI_STORAGE__SQL_DIALECT=postgres\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      # - LHCI_STORAGE__SQL_CONNECTION_URL=postgresql://user:pass@db:5432/lhci\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">    restart\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">unless-stopped\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">volumes\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#85E89D\">  lhci-data\u003C/span>\u003Cspan style=\"color:#E1E4E8\">:\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Après le déploiement, créez un projet et récupérez le build token :\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Création du projet sur le LHCI Server\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">lhci\u003C/span>\u003Cspan style=\"color:#9ECBFF\"> wizard\u003C/span>\u003Cspan style=\"color:#79B8FF\"> --wizard=new-project\u003C/span>\u003Cspan style=\"color:#79B8FF\"> --serverBaseUrl=https://lhci.votredomaine.com\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Output :\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Created project \"mon-ecommerce\" (id: xxxxx)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Build token: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Admin token: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\"># Use the build token in your CI config\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>Le dashboard LHCI Server est minimaliste mais fonctionnel. Il affiche les courbes de chaque métrique par URL au fil des builds, avec un diff visuel entre deux runs. C'est suffisant pour repérer une dégradation progressive du Speed Index sur trois semaines — le type de régression invisible dans un audit ponctuel.\u003C/p>\n\u003Ch3>Limites du LHCI Server\u003C/h3>\n\u003Cp>Le dashboard LHCI est centré sur la performance Lighthouse. Il ne corrèle pas avec vos données de trafic organique, ne surveille pas les changements de contenu (meta descriptions supprimées, canonicals cassées en production), et n'alerte pas en temps réel.\u003C/p>\n\u003Cp>C'est un outil de gate CI, pas un outil de monitoring SEO complet. Pour la détection continue de \u003Ca href=\"/blog/regressions-seo-les-10-types-les-plus-frequents\">régressions SEO en production\u003C/a> — metas disparues, erreurs de SSR, backlinks perdus — un outil de monitoring dédié comme Seogard complète LHCI en couvrant ce que le pipeline CI ne voit pas : les problèmes qui surviennent après le déploiement, côté infrastructure ou côté données.\u003C/p>\n\u003Ch2>Scénario réel : migration SSR d'un e-commerce\u003C/h2>\n\u003Cp>Prenons un cas concret. Un e-commerce spécialisé outdoor — 15 000 pages produit, 800 pages catégories, 200 articles de blog — migre de Create React App (CSR pur) vers Next.js avec SSR. L'objectif : améliorer l'indexabilité et les Core Web Vitals.\u003C/p>\n\u003Ch3>Avant la migration\u003C/h3>\n\u003Cp>Les scores Lighthouse sur les pages clés (mesurés avec Chrome DevTools en émulation mobile) :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>Homepage\u003C/strong> : Performance 42, LCP 5.8s, CLS 0.32\u003C/li>\n\u003Cli>\u003Cstrong>Page catégorie\u003C/strong> : Performance 28, LCP 7.1s (hydratation tardive du listing produit)\u003C/li>\n\u003Cli>\u003Cstrong>Page produit\u003C/strong> : Performance 35, LCP 6.4s (image hero chargée côté client)\u003C/li>\n\u003C/ul>\n\u003Cp>Le \u003Ca href=\"/blog/comparer-ssr-et-csr-detecter-les-divergences-invisibles\">rendu CSR causait des divergences majeures\u003C/a> entre ce que Googlebot voyait et ce que les utilisateurs expérimentaient. Les pages étaient techniquement indexées, mais avec un contenu partiel — les données structurées Product étaient injectées côté client et irrégulièrement captées par le crawler.\u003C/p>\n\u003Ch3>Configuration LHCI pendant la migration\u003C/h3>\n\u003Cp>L'équipe configure LHCI dès le premier sprint de migration, avec des seuils progressifs :\u003C/p>\n\u003Cp>\u003Cstrong>Sprint 1-3 (fondations Next.js)\u003C/strong> : assertions en \u003Ccode>warn\u003C/code> uniquement, seuils larges. L'objectif est de collecter une baseline.\u003C/p>\n\u003Cp>\u003Cstrong>Sprint 4-6 (optimisation SSR)\u003C/strong> : passage en \u003Ccode>error\u003C/code> pour le LCP (&#x3C; 3500ms), le CLS (&#x3C; 0.15), et les audits SEO Lighthouse.\u003C/p>\n\u003Cp>\u003Cstrong>Sprint 7+ (fine-tuning)\u003C/strong> : seuils resserrés vers les objectifs finaux — LCP &#x3C; 2500ms, CLS &#x3C; 0.1, TBT &#x3C; 300ms.\u003C/p>\n\u003Ch3>Le bug détecté par LHCI\u003C/h3>\n\u003Cp>Au sprint 5, un développeur refactore le composant de lazy loading des images produit. Le nouveau code utilise \u003Ccode>loading=\"lazy\"\u003C/code> sur l'image hero above the fold — une erreur classique qui empêche le navigateur de prioriser le LCP element.\u003C/p>\n\u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// ❌ Code problématique détecté par LHCI\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">// Le composant ProductImage appliquait loading=\"lazy\" à toutes les images\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">const\u003C/span>\u003Cspan style=\"color:#B392F0\"> ProductImage\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> React\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">FC\u003C/span>\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#B392F0\">ProductImageProps\u003C/span>\u003Cspan style=\"color:#E1E4E8\">> \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ({ \u003C/span>\u003Cspan style=\"color:#FFAB70\">src\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">alt\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">priority\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }) \u003C/span>\u003Cspan style=\"color:#F97583\">=>\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    &#x3C;\u003C/span>\u003Cspan style=\"color:#FFAB70\">Image\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      src\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{src}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      alt\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{alt}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      width\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003Cspan style=\"color:#79B8FF\">800\u003C/span>\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      height\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003Cspan style=\"color:#79B8FF\">600\u003C/span>\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      loading\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"lazy\"\u003C/span>\u003Cspan style=\"color:#6A737D\">     // ← Appliqué même à l'image hero (priority=true)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      decoding\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#9ECBFF\">\"async\"\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    />\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\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:#6A737D\">// ✅ Correction après l'alerte LHCI\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">const\u003C/span>\u003Cspan style=\"color:#B392F0\"> ProductImage\u003C/span>\u003Cspan style=\"color:#F97583\">:\u003C/span>\u003Cspan style=\"color:#B392F0\"> React\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#B392F0\">FC\u003C/span>\u003Cspan style=\"color:#E1E4E8\">&#x3C;\u003C/span>\u003Cspan style=\"color:#B392F0\">ProductImageProps\u003C/span>\u003Cspan style=\"color:#E1E4E8\">> \u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> ({ \u003C/span>\u003Cspan style=\"color:#FFAB70\">src\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">alt\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, \u003C/span>\u003Cspan style=\"color:#FFAB70\">priority\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }) \u003C/span>\u003Cspan style=\"color:#F97583\">=>\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">  return\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> (\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    &#x3C;\u003C/span>\u003Cspan style=\"color:#FFAB70\">Image\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      src\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{src}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      alt\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{alt}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      width\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003Cspan style=\"color:#79B8FF\">800\u003C/span>\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      height\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{\u003C/span>\u003Cspan style=\"color:#79B8FF\">600\u003C/span>\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      loading\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{priority ? \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'eager'\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> : \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'lazy'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      decoding\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{priority ? \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'sync'\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> : \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'async'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      // Next.js Image avec priority={true} gère automatiquement le preload\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      priority\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{priority}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      // fetchPriority pour les navigateurs qui le supportent\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      fetchPriority\u003C/span>\u003Cspan style=\"color:#F97583\">=\u003C/span>\u003Cspan style=\"color:#E1E4E8\">{priority ? \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'high'\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> : \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'auto'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">}\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    />\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\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\u003Cp>LHCI a bloqué le merge : le LCP de la page produit est passé de 2.1s à 3.8s entre la branche \u003Ccode>main\u003C/code> et la PR. Le développeur a corrigé en 10 minutes. Sans LHCI, ce bug aurait été déployé un vendredi (comme c'est \u003Ca href=\"/blog/deploiement-vendredi-soir-comment-eviter-la-catastrophe-seo\">malheureusement souvent le cas\u003C/a>) et serait resté en production jusqu'au prochain audit manuel.\u003C/p>\n\u003Ch3>Résultats post-migration\u003C/h3>\n\u003Cp>Après 8 sprints et ~200 builds LHCI :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>LCP moyen pages produit\u003C/strong> : 6.4s → 1.9s\u003C/li>\n\u003Cli>\u003Cstrong>CLS moyen pages catégories\u003C/strong> : 0.32 → 0.04\u003C/li>\n\u003Cli>\u003Cstrong>Score performance Lighthouse\u003C/strong> : 28-42 → 78-91 selon le template\u003C/li>\n\u003Cli>\u003Cstrong>Pages indexées dans Search Console\u003C/strong> : +23% en 6 semaines (les pages auparavant rendues partiellement sont maintenant crawlées avec leur contenu complet)\u003C/li>\n\u003C/ul>\n\u003Cp>Le trafic organique a augmenté de 31% sur les 3 mois suivant la fin de la migration — mais il est impossible d'attribuer ce gain uniquement aux Core Web Vitals. L'amélioration du SSR a aussi résolu des problèmes d'indexation du contenu. C'est la réalité du SEO technique : les optimisations s'empilent et leurs effets sont corrélés.\u003C/p>\n\u003Ch2>Stratégies avancées de configuration\u003C/h2>\n\u003Ch3>Assertions budgétaires avec performance budgets\u003C/h3>\n\u003Cp>Au-delà des scores Lighthouse, LHCI supporte les \u003Ca href=\"https://web.dev/articles/performance-budgets-101\">performance budgets\u003C/a> — des limites sur le poids des ressources :\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.js — section assert\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">assert\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#B392F0\">  assertions\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'resource-summary:script:size'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">350000\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],   \u003C/span>\u003Cspan style=\"color:#6A737D\">// JS &#x3C; 350KB\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'resource-summary:image:size'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'warn'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">500000\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],     \u003C/span>\u003Cspan style=\"color:#6A737D\">// Images &#x3C; 500KB\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'resource-summary:third-party:count'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'warn'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">8\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],   \u003C/span>\u003Cspan style=\"color:#6A737D\">// Max 8 third-parties\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'resource-summary:total:size'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">1500000\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],   \u003C/span>\u003Cspan style=\"color:#6A737D\">// Total &#x3C; 1.5MB\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">    'unused-javascript'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'warn'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxLength: \u003C/span>\u003Cspan style=\"color:#79B8FF\">2\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],                          \u003C/span>\u003Cspan style=\"color:#6A737D\">// Max 2 scripts inutilisés\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\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\u003Cp>Ces budgets détectent une catégorie de régressions que les scores Lighthouse ne montrent pas toujours : un bundle JS qui grossit de 20KB à chaque sprint. Le score reste à 85, mais le bundle passe de 200KB à 450KB en 6 mois. Un jour, le score décroche brutalement — et il est trop tard pour identifier quel commit a ajouté les 250KB.\u003C/p>\n\u003Ch3>Audits conditionnels par type de page\u003C/h3>\n\u003Cp>Si votre site a des templates aux caractéristiques très différentes (pages produit avec beaucoup d'images vs. articles de blog textuels), des seuils uniques sont trop permissifs pour certains templates et trop stricts pour d'autres.\u003C/p>\n\u003Cp>La solution : plusieurs configurations LHCI avec des overrides par URL pattern.\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.js\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">module\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#79B8FF\">exports\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  ci: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    collect: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      url: [\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'http://localhost:3000/'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'http://localhost:3000/c/chaussures-running'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'http://localhost:3000/p/nike-pegasus-41-noir'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'http://localhost:3000/blog/guide-pronation'\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\">      numberOfRuns: \u003C/span>\u003Cspan style=\"color:#79B8FF\">5\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      startServerCommand: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'npm run start'\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\">    assert: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      assertions: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">        // Seuils par défaut\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'categories:performance'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { minScore: \u003C/span>\u003Cspan style=\"color:#79B8FF\">0.7\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'largest-contentful-paint'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">2500\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\">      assertMatrix: [\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">          // Pages produit : LCP plus strict (image hero optimisée attendue)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          matchingUrlPattern: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'.*/p/.*'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          assertions: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'largest-contentful-paint'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">2200\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'cumulative-layout-shift'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">0.05\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\">        },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">          // Pages catégorie : tolérance plus large (listings dynamiques)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          matchingUrlPattern: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'.*/c/.*'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          assertions: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'largest-contentful-paint'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">2800\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'total-blocking-time'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">400\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\">        },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">        {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">          // Blog : performance stricte (pages légères)\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          matchingUrlPattern: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'.*/blog/.*'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">          assertions: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'categories:performance'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { minScore: \u003C/span>\u003Cspan style=\"color:#79B8FF\">0.9\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> }],\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">            'largest-contentful-paint'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">: [\u003C/span>\u003Cspan style=\"color:#9ECBFF\">'error'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">, { maxNumericValue: \u003C/span>\u003Cspan style=\"color:#79B8FF\">1800\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\">        },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\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\">    upload: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      target: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'lhci'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      serverBaseUrl: \u003C/span>\u003Cspan style=\"color:#9ECBFF\">'https://lhci.votredomaine.com'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      token: process.env.\u003C/span>\u003Cspan style=\"color:#79B8FF\">LHCI_BUILD_TOKEN\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\">  },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">};\u003C/span>\u003C/span>\u003C/code>\u003C/pre>\n\u003Cp>L'\u003Ccode>assertMatrix\u003C/code> est une fonctionnalité puissante mais peu documentée. Elle permet de définir des contrats de performance différents par template — exactement ce dont un site de 15K+ pages a besoin.\u003C/p>\n\u003Ch3>Intégration avec Screaming Frog et Search Console\u003C/h3>\n\u003Cp>LHCI couvre le monitoring pré-déploiement. Mais pour valider l'impact en production, croisez les données :\u003C/p>\n\u003Cp>\u003Cstrong>Search Console\u003C/strong> : le rapport Core Web Vitals (sous \"Expérience\") montre les données CrUX réelles. Après une amélioration détectée par LHCI en synthétique, vérifiez que les métriques terrain suivent dans les 28 jours suivants. Si LHCI montre un LCP à 2.0s mais CrUX reste à 3.5s, votre environnement CI ne reproduit pas les conditions réelles (serveur plus rapide, CDN absent, etc.). Consultez les \u003Ca href=\"/blog/google-search-console-les-rapports-que-vous-ignorez\">rapports Search Console souvent négligés\u003C/a> pour exploiter pleinement ces données.\u003C/p>\n\u003Cp>\u003Cstrong>Screaming Frog\u003C/strong> : pour auditer les URLs que LHCI ne couvre pas. Un crawl Screaming Frog de vos 15K pages avec l'intégration PageSpeed Insights activée prend ~4-6 heures (rate limited à ~2 requêtes/seconde par l'API PSI). Faites-le mensuellement pour identifier les templates ou pages spécifiques où la performance dévie de votre échantillon LHCI.\u003C/p>\n\u003Cp>\u003Cstrong>Chrome DevTools\u003C/strong> : pour le diagnostic post-régression. Quand LHCI détecte un LCP dégradé, utilisez le panel Performance de Chrome DevTools pour tracer l'origine exacte — est-ce le serveur (TTFB), le rendering, ou un script tiers ? Les \u003Ca href=\"/blog/chrome-devtools-pour-le-seo-astuces-avancees\">techniques avancées de Chrome DevTools pour le SEO\u003C/a> couvrent ces workflows en détail.\u003C/p>\n\u003Ch2>Les pièges courants et comment les éviter\u003C/h2>\n\u003Ch3>Variance des résultats en CI\u003C/h3>\n\u003Cp>Le piège numéro un : des tests flaky qui échouent aléatoirement. Un runner CI partagé avec d'autres jobs, un réseau instable, un garbage collection Chrome mal tombé — et votre LCP varie de 800ms entre deux runs identiques.\u003C/p>\n\u003Cp>Solutions concrètes :\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>5 runs minimum\u003C/strong> avec prise de la médiane (pas la moyenne)\u003C/li>\n\u003Cli>\u003Cstrong>Runner dédié\u003C/strong> ou at minimum un runner avec des ressources CPU/RAM prévisibles\u003C/li>\n\u003Cli>\u003Cstrong>Seuils avec marge\u003C/strong> : si votre objectif réel est un LCP de 2.5s, mettez le seuil LHCI à 2.8s ou 3.0s pour absorber la variance synthétique\u003C/li>\n\u003Cli>\u003Cstrong>\u003Ccode>--chrome-flags=\"--deterministic-mode\"\u003C/code>\u003C/strong> : réduit la variance mais n'est pas supporté dans toutes les versions de Chrome\u003C/li>\n\u003C/ul>\n\u003Ch3>Tester contre localhost vs. staging\u003C/h3>\n\u003Cp>LHCI teste typiquement un serveur local (\u003Ccode>startServerCommand\u003C/code>). Cela signifie : pas de CDN, pas de latence réseau réelle, pas de charge concurrente. Votre LCP local à 1.2s deviendra 2.4s en production derrière un CDN mal configuré ou avec des tiers qui ajoutent 500ms.\u003C/p>\n\u003Cp>Pour les projets matures, ajoutez un second job LHCI qui teste contre l'environnement de staging après déploiement :\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.staging.js\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">module\u003C/span>\u003Cspan style=\"color:#E1E4E8\">.\u003C/span>\u003Cspan style=\"color:#79B8FF\">exports\u003C/span>\u003Cspan style=\"color:#F97583\"> =\u003C/span>\u003Cspan style=\"color:#E1E4E8\"> {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">  ci: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    collect: {\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">      url: [\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'https://staging.votredomaine.com/'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'https://staging.votredomaine.com/c/chaussures-running'\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#9ECBFF\">        'https://staging.votredomaine.com/p/nike-pegasus-41-noir'\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\">      numberOfRuns: \u003C/span>\u003Cspan style=\"color:#79B8FF\">3\u003C/span>\u003Cspan style=\"color:#E1E4E8\">,\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">      // Pas de startServerCommand — on teste un environnement distant\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    },\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#6A737D\">    // ... assertions et upload identiques\u003C/span>\u003C/span>\n\u003Cspan class=\"line\">\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\u003Cp>C'est plus réaliste, mais plus lent et dépendant de l'infrastructure staging. Le meilleur setup combine les deux : LHCI local en gate sur les PR (feedback rapide), LHCI staging post-deploy sur \u003Ccode>main\u003C/code> (validation réaliste).\u003C/p>\n\u003Ch3>Ne pas auditer les bonnes pages\u003C/h3>\n\u003Cp>Si vous n'auditez que la homepage, vous manquerez la régression du composant de listing produit qui affecte 800 pages catégories. Inversement, auditer 50 URLs différentes ralentit le pipeline au point de devenir inutile.\u003C/p>\n\u003Cp>La règle : \u003Cstrong>une URL par template, pondérée par le trafic\u003C/strong>. Identifiez dans Search Console vos 4-5 templates qui génèrent 80% du trafic organique. Choisissez l'URL la plus représentative de chaque template (ni la plus légère, ni la plus lourde — la médiane).\u003C/p>\n\u003Ch2>Complémentarité avec le monitoring continu\u003C/h2>\n\u003Cp>LHCI est un garde-fou pré-déploiement. Il répond à la question : \"est-ce que ce changement de code dégrade la performance ?\" Mais il ne répond pas à : \"est-ce que la performance en production est stable cette semaine ?\"\u003C/p>\n\u003Cp>Les régressions qui échappent à LHCI :\u003C/p>\n\u003Cul>\n\u003Cli>Un tiers (analytics, A/B testing, consent manager) qui ralentit après une mise à jour côté provider\u003C/li>\n\u003Cli>Un CDN qui expire un cache et force des recalculs serveur\u003C/li>\n\u003Cli>Une base de données qui ralentit sous la charge et dégrade le TTFB\u003C/li>\n\u003Cli>Un changement de configuration Nginx/Cloudflare fait par l'ops sans passer par le pipeline CI\u003C/li>\n\u003C/ul>\n\u003Cp>Ces régressions surviennent en production, sans changement de code. \u003Ca href=\"/blog/monitoring-seo-pourquoi-les-audits-ponctuels-ne-suffisent-plus\">Les audits ponctuels ne les détectent pas\u003C/a> — seul un monitoring continu en production le peut.\u003C/p>\n\u003Cp>L'approche robuste combine trois couches : LHCI dans le CI/CD pour les régressions code, des données RUM (Real User Monitoring) pour les performances terrain, et un \u003Ca href=\"/blog/automatiser-les-checks-seo-dans-le-ci-cd\">pipeline d'automatisation SEO dans le CI/CD\u003C/a> qui couvre les aspects non-performance (metas, canonicals, robots, structured data).\u003C/p>\n\u003Cp>LHCI est un outil de développeur, pas un outil de monitoring SEO. Il fait très bien son job — bloquer les régressions de performance au niveau du code — mais il ne remplace ni la Search Console, ni Screaming Frog, ni un monitoring de production continu. Utilisez-le comme première ligne de défense, et complétez avec des outils qui surveillent ce qui se passe réellement sur vos pages en production, 24/7.\u003C/p>\n\u003Cpre>\u003Ccode>\u003C/code>\u003C/pre>",null,12,[18,19,20,21,22],"lighthouse","ci","performance","monitoring","core-web-vitals","Lighthouse CI : monitoring performance continu en production","Fri Apr 03 2026 16:02:42 GMT+0000 (Coordinated Universal Time)",[]]