Un score Lighthouse de 92 ne vous dit pas si votre <title> vient de passer en USD par défaut côté CSR. Il note une page, isolément, sur un instant T. Ce dont vous avez besoin en pipeline, c'est d'une décision binaire : ce build part en prod, ou il ne part pas. Cet article décrit comment câbler un webhook qui rend un verdict pass/fail sur la régression SEO, avec trois niveaux de strictness, pour bloquer un déploiement avant que Googlebot ne crawle la casse.
Pourquoi un score ne bloque rien (et un verdict, oui)
Lighthouse et les checks synthétiques génériques produisent une métrique continue. Un score. Or un score ne prend pas de décision. Vous devez quand même définir un seuil, l'interpréter, et décider manuellement. En pipeline, ça ne tient pas : personne ne lit un rapport Lighthouse à chaque merge sur main.
Le problème est aussi que le score Lighthouse mesure surtout la performance et l'accessibilité d'une URL. Il ne compare pas le HTML rendu vs le HTML attendu entre deux versions. Une régression SEO typique n'est pas une perte de performance : c'est une balise qui disparaît, un noindex qui apparaît, un hreflang qui pointe vers un domaine mort. Ces régressions passent un audit Lighthouse à 90+ sans broncher.
Le verdict pass/fail inverse la logique. Au lieu de produire un chiffre à interpréter, le moteur compare l'état SEO du build candidat à une baseline (la prod actuelle ou un snapshot validé) et tranche. Pas de gris. Soit les invariants SEO tiennent, soit le webhook renvoie fail et le déploiement s'arrête.
L'architecture : webhook + moteur de décision
Le découplage est important. Votre CI (GitHub Actions, GitLab CI, Jenkins) ne doit pas embarquer la logique SEO. Il appelle un endpoint, attend une réponse, et agit dessus.
L'application réceptrice reçoit cette requête, traite la charge utile (payload) et exécute une action prédéfinie — ici, rendre un verdict. Le flux :
- Le build candidat est déployé sur un environnement de preview (URL éphémère).
- La fin du build déclenche un webhook
POSTvers le moteur de décision, avec l'URL de preview et le commit SHA dans le payload. - Le moteur crawle la preview, compare à la baseline, applique le niveau de strictness configuré.
- Il répond
{ "verdict": "pass" | "fail", "violations": [...] }. - Le job CI lit le verdict.
fail→ exit non-zéro → le promote en prod est bloqué.
Sécurisez ce webhook. La sécurité d'un webhook ne se résume pas à un endpoint public caché derrière une URL difficile à deviner. Il faut vérifier l'origine, valider la charge utile, limiter les réémissions et empêcher les replays qui pourraient rejouer un événement déjà traité. Signez le payload en HMAC et vérifiez la signature au bord, sinon n'importe qui peut forger un pass.
Un point de fiabilité : le webhook est en mode « fire and forget ». Si votre moteur ne répond pas, ne traitez pas l'absence de réponse comme un pass. Timeout = fail. Un deploy ne doit jamais passer parce que le check n'a pas pu s'exécuter.
Les 3 niveaux de strictness
C'est ici que le verdict devient exploitable selon le contexte (branche, environnement, criticité). Trois niveaux :
Niveau 1 — block (bloquant dur)
Réservé aux régressions qui cassent l'indexation. Toute violation rend immédiatement fail. Exemples d'invariants en mode block :
- Apparition d'un
<meta name="robots" content="noindex">sur une URL indexable de la baseline. <title>ou<h1>vide, ou disparu côté HTML brut SSR.- Status code passant de 200 à 3xx/4xx/5xx sur une URL stratégique.
rel=canonicalpointant vers un domaine différent ou supprimé.
Niveau 2 — warn (signalement non bloquant)
Le verdict reste pass, mais les violations sont remontées dans le rapport et postées (Slack, PR comment). Pour les régressions qui méritent un œil humain sans justifier un blocage : variation de longueur de meta description, perte d'un attribut alt, changement mineur de structure Hn.
Niveau 3 — audit (observation)
Aucun impact sur le verdict. Le moteur enregistre l'état pour construire l'historique et détecter les dérives lentes sur plusieurs déploiements. Utile sur les branches de feature où vous voulez de la donnée sans friction.
La règle d'or, héritée de la gouvernance des flux : Ce cadre doit rester simple à expliquer. Un seuil qui ne peut pas être rappelé en une phrase dans un échange support ou métier n'est souvent pas un bon seuil. La règle doit permettre de dire immédiatement si l'on poursuit, si l'on isole ou si l'on bloque. Si vous ne pouvez pas dire en une phrase pourquoi un build a été refusé, votre strictness est mal calibrée.
Le différenciateur : comparer SSR vs CSR avant le verdict
C'est l'angle qu'aucun check générique ne couvre. Une régression SEO moderne se cache souvent dans l'écart entre le HTML brut (ce que Googlebot lit au premier passage) et le DOM rendu après exécution JS.
Exemple concret de règle du crawler Seogard : le moteur récupère le HTML brut SSR et le DOM rendu CSR, puis compare des invariants critiques entre les deux. Si le <title> côté SSR affiche le bon libellé mais que le rendu CSR le réécrit avec une devise par défaut (un dropdown multi-devises qui force USD), Googlebot indexe potentiellement la version SSR pendant que l'utilisateur voit autre chose — ou pire, le contraire. Cette règle rend fail en mode block parce que la divergence SSR/CSR sur le <title> est une régression invisible à Lighthouse comme aux checks synthétiques classiques.
Ce type de détection vit dans le moteur de décision, pas dans le runner CI. C'est précisément ce que Seogard fait tourner : comparaison HTML brut SSR vs rendu JS, détection de la régression avant Google, et verdict pass/fail câblé sur votre pipeline. Pour le câblage côté runner GitHub, voyez comment faire échouer un job sur un déploiement régressif ; et pour le cas spécifique du noindex, le détecter en production avant Google.
Calibrer pour éviter les faux positifs
Le piège du pass/fail bloquant, c'est le faux positif qui paralyse les équipes et finit par être désactivé. Pour l'éviter :
- Démarrez en
auditpartout. Collectez deux semaines de données avant de promouvoir des invariants enblock. - N'élevez en
blockque ce qui casse l'indexation, pas ce qui dégrade le confort. Unnoindexaccidentel : block. Une meta description de 30 caractères : warn. - Scopez les invariants par type de page. Une page produit et un article de blog n'ont pas les mêmes invariants Hn ou canonical.
- Ignorez le bruit attendu. Un timestamp dynamique ou un nonce CSP qui change à chaque render ne sont pas des régressions.
Comme pour tout flux piloté par webhook : Le déploiement le plus sain ne cherche pas à tout ouvrir d'un coup. Il avance par étapes courtes, vérifie la reprise à chaque changement et n'étend le flux qu'après une preuve stable en production réelle. Élargissez votre liste d'invariants bloquants progressivement, build après build.
Checklist de mise en place
- Endpoint webhook signé HMAC, signature vérifiée au bord.
- Payload : URL de preview + commit SHA + environnement cible.
- Baseline définie (snapshot prod validé).
- Invariants classés en
block/warn/audit. - Timeout du moteur traité comme
fail, jamais commepass. - Comparaison SSR vs CSR active sur
<title>, canonical, robots. - Verdict
failcâblé sur exit non-zéro du job de promote.
Un score vous informe. Un verdict vous protège. La différence entre les deux, c'est un deploy régressif qui part en prod un vendredi soir — ou qui ne part pas.
Pour aller plus loin sur les sources officielles : Google Search Central — URL Inspection pour vérifier ce que Google voit réellement, et la doc GitHub sur le troubleshooting des webhooks pour fiabiliser la livraison.