Intégrez un audit SEO dans votre pipeline de déploiement : API, codes de sortie et recettes CI
Une recette fonctionnelle pour auditer votre site depuis CI : trois points de terminaison REST, les champs de score qu'un portail de construction peut lire, et la différence entre une vraie régression et un léger incident d'infrastructure.
Les régressions techniques qui coûtent le plus de visibilité restent invisibles lors d'une revue de code. Une balise canonical qui commence à pointer vers le mauvais hôte, un bloc hreflang qui perd sa référence propre, un en-tête Content-Security-Policy supprimé d'une configuration de reverse-proxy, une route qui déplace son contenu derrière l'hydratation — chacun de ces éléments apparaît dans un diff qui semble correct et passe tous les tests que vous avez. Ils apparaissent des semaines plus tard, dans un graphique de trafic, bien après le commit qui les a causés, défilé hors de vue.
La solution est structurelle : exécuter l'audit au déploiement, pas le jour où quelqu'un se souvient de. Chaque rapport SEOReport est disponible sur REST, la réponse contient un bloc de score numérique, et un script shell peut transformer ce bloc en code de sortie. C'est tout le mécanisme.
Quatre classes de régression que seule une machine détecte à temps
Quatre modes d'échec survivent particulièrement bien à la revue humaine, et il vaut la peine de les nommer concrètement, car c'est ce qu'un portail protège réellement.
Hreflang. Un cluster dépend de la référence propre, des liens de retour, d'un x-default judicieux, d'URLs bien formées, et d'alternatives indexables dont le canonique pointe vers elles-mêmes. Une migration CMS qui réécrit les motifs URL peut casser les liens de retour dans chaque langue à la fois, et aucune langue ne semble cassée isolément.
Canonicalisation. Un canonique doit exister, pointer vers une cible indexable qui ne redirige pas, et rester sur l'hôte propre du site. La pire version de cette défaillance est un canonique de staging livré en production — une page qui nomme discrètement un hôte que vous ne voulez pas indexer.
En-têtes de sécurité. Content-Security-Policy, HSTS, Referrer-Policy, X-Frame-Options, X-Content-Type-Options, et Permissions-Policy. Ils résident dans la configuration d'infrastructure, pas dans le code d'application, ce qui explique exactement pourquoi ils disparaissent lors d'un changement de proxy ou de configuration edge et que personne ne les remarque.
Parité de rendu. Une page peut livrer une version dans sa réponse HTML et une autre après l'exécution des scripts : un titre différent, des titres manquants, un canonique injecté tardivement, ou un texte de corps qui n'existe qu'après l'hydratation. Lorsque nos données de parité de rendu publiées ont atteint un verdict, la plupart des sites servaient une page matériellement plus fine à tout ce qui lit HTML tel que délivré. Un ssr: false dans un fichier de configuration suffit pour déplacer un site dans ce groupe.
Les quatre sont déterministes, les quatre sont peu coûteux à vérifier, et les quatre sont le genre de chose qu'une personne n'inspecte que lorsqu'elle est déjà suspicieuse.
Trois points de terminaison et une boucle
La surface API qu'un pipeline nécessite est petite. L'accès API est inclus dans les plans payants et s'authentifie avec un jeton porteur : émettez une clé depuis le tableau de bord, et envoyez-la comme Authorization: Bearer sr__live_your_api_key. La même clé ouvre REST et MCP ; la référence complète se trouve sur la page développeurs.
Soumettre un rapport est un POST :
JOB_ID=$(curl -sS -X POST https://seoreport.dev/api/v1/reports \-H "Authorization: Bearer $SEOREPORT_API_KEY" \-H "Content-Type: application/json" \-d '{"url": "https://example.com", "forceRerun": true}' \| jq -r '.report.jobId')
Deux choses à propos de ce corps comptent spécifiquement en CI.
forceRerun existe parce que le API réutilise un instantané existant lorsqu'un est disponible — logique pour une personne cliquant sur un bouton, erroné pour une porte de déploiement, qui autrement noterait la version précédente. La réponse vous indique ce qui s'est passé dans submission.reusedSnapshot. Les exécutions fraîches forcées sont une capacité payante ; une clé sans cette habilitation reçoit un 403 indiquant la raison plutôt que de retourner silencieusement des données obsolètes.
La portée de l'audit est déduite du chemin URL. Un origine vierge exécute un audit complet du site ; un URL avec un chemin audit cette page en isolation. Ainsi, un pipeline peut verrouiller la page d'accueil à chaque déploiement et ajouter la route spécifique touchée par un changement, sans aucun paramètre supplémentaire.
Puis poll. GET /api/v1/reports/:id/ready est le point de terminaison d'état bon marché — il renvoie ready, status, stage, pollAfterMs, et, lorsqu'une exécution échoue, un objet error :
for _ in $(seq 1 60); doREADY=$(curl -sS -H "Authorization: Bearer $SEOREPORT_API_KEY" \"https://seoreport.dev/api/v1/reports/$JOB_ID/ready")CODE=$(echo "$READY" | jq -r '.error.code // empty')if [ -n "$CODE" ]; thenecho "audit did not complete: $CODE"exit 75fi[ "$(echo "$READY" | jq -r '.ready')" = "true" ] && breaksleep 5done
Ce exit 75 est intentionnel, et c'est la partie que la plupart des intégrations interprètent mal.
Différencier une régression d'une exécution qui n'a jamais eu lieu
error.code porte une raison lisible par machine et un booléen retryable. DNS_FAILURE, CRAWLER_BLOCKED, REDIRECT_LOOP, TLS_ERROR, NO_CONTENT, et STALE_ENGINE_VERSION sont marqués non réessayables, car réessayer produit la même réponse. La plupart de cette liste décrit une condition sur le site, et plusieurs entrées — une boucle de redirection, une erreur TLS, une règle de bot qui bloque désormais les crawlers — sont de véritables défauts de déploiement qui valent la peine d'échouer une construction. Les codes hors de cet ensemble sont transitoires et valent une autre tentative.
Un verrou qui fusionne « le site a régressé » et « l'audit n'a pas pu s'exécuter » en la même construction rouge est désactivé dans un mois. Gardez-les séparés dans vos codes de sortie : 1 pour un verdict que vous avez demandé au verrou d'appliquer, 75 — le conventionnel EX_TEMPFAIL — pour une exécution qui n'a produit aucun verdict. La plupart des systèmes CI peuvent être configurés pour réessayer le second et alerter un humain pour le premier.
Lire le bloc de score
Une fois ready vrai, GET /api/v1/reports/:id renvoie l'objet rapport. Le bloc score est la partie que le verrou lit :
REPORT=$(curl -sS -H "Authorization: Bearer $SEOREPORT_API_KEY" \"https://seoreport.dev/api/v1/reports/$JOB_ID")echo "$REPORT" | jq '.report.score| {overall, totalChecks, passedChecks, failedChecks,warnChecks, inconclusiveChecks, failingCriticalChecks}'
Trois de ces champs portent la plupart du signal.
failingCriticalChecks est le compte des échecs que le moteur classe comme critiques — ceux capables de retirer des pages d'un index ou de cacher du contenu à un crawler entièrement. Pour un premier verrou, c'est le seul nombre dont vous avez besoin, et > 0 est un seuil défendable le premier jour.
overall est le score composite, utile comme cric : stockez la valeur du dernier déploiement et échouez lorsque la nouvelle chute de plus qu'une tolérance que vous choisissez. Cela attrape l'érosion lente que aucune vérification unique ne signale.
domainScores décompose le score par seo, ai, performance, security, et brand, chacun avec son propre compte pass, fail, et warn. Les planchers par domaine permettent à différentes équipes de posséder des budgets différents — le score de sécurité est un verrou significatif pour quiconque possède la configuration edge, indépendamment de tout ce que l'équipe de contenu expédie.
inconclusiveChecks mérite sa propre règle : ne jamais le verrouiller. Un contrôle indique inconclusif lorsque le moteur ne peut pas atteindre un verdict défendable, et traiter cela comme un échec entraîne tout le monde à ignorer la porte.
Le bloc de score fait partie du résumé du rapport, donc une porte de construction peut le lire sans récupérer les résultats complets. Lorsque vous voulez la charge utile complète pour l'archivage, GET /api/v1/reports/:id/result renvoie les résultats complets, et GET /api/v1/reports/:id/download?format=json renvoie le même objet comme un artefact téléchargeable — utile à attacher à la construction afin que la différence entre deux déploiements soit inspectable plus tard.
L'intégrer dans un flux de travail
Rien ci-dessus n'est spécifique au fournisseur CI. Dans GitHub Actions, toute la porte est une étape appelant le script que vous avez déjà :
- name: SEO audit gateenv:SEOREPORT_API_KEY: ${{ secrets.SEOREPORT_API_KEY }}AUDIT_URL: https://example.comrun: ./scripts/seo-gate.sh
Où seo-gate.sh soumet, interroge et se termine par la décision :
CRITICAL=$(echo "$REPORT" | jq -r '.report.score.failingCriticalChecks // 0')SECURITY=$(echo "$REPORT" | jq -r \'.report.score.domainScores[] | select(.domain == "security") | .score')if [ "$CRITICAL" -gt 0 ]; thenecho "::error::$CRITICAL critical checks failing"exit 1fiecho "clean — security domain at $SECURITY"
Exécutez-le après la fin du déploiement plutôt que contre un environnement d'aperçu. Un aperçu URL est généralement derrière une authentification de base ou un défi de bot, et un audit qui ne peut pas récupérer la page signale CRAWLER_BLOCKED plutôt qu'un score. Post-déploiement contre l'origine réelle est à la fois plus simple et plus proche de ce que vit un crawler.
Deux formes adjacentes valent la peine d'être connues. Si votre orchestration vit déjà sur une plateforme de scraping, notre acteur Apify exécute le même moteur sur un horaire sans aucune de cette colle. Et pour les agents, la même clé porteuse authentifie une connexion MCP à https://seoreport.dev/mcp, où les outils de rapport sont annoncés sur connect — un agent enquêtant sur une baisse de trafic peut exécuter l'audit et lire les conclusions lui-même, ce qui est documenté à côté de la surface REST sur la page développeurs.
Ce que la porte ne couvre pas
Une porte de déploiement répond à une question : cette version a-t-elle introduit une régression. Elle reste silencieuse sur tout ce qui change sans déploiement — un certificat expirant, une configuration CDN modifiée dans un tableau de bord, un script tiers qui commence à bloquer le rendu, la migration d'un concurrent qui change ce avec quoi votre canonique entre en collision. La surveillance par domaine est le complément toujours actif à la porte : la surveillance SEO API ré-audit un domaine enregistré chaque semaine avec le même compte et la même clé.
Commencez par la version étroite. Un point de terminaison, un champ, failingCriticalChecks > 0, et un honnête exit 75 lorsque l'audit n'a pas pu fonctionner du tout. Une porte qui se déclenche deux fois par an et est fiable à chaque fois vaut plus qu'une porte complète que tout le monde a appris à ignorer.
Obtenez le diagnostic complet de votre site
Un rapport fondé sur des preuves et un plan d’action priorisé, avec une offre à crédits mensuels.