デプロイパイプラインにSEO監査を組み込む:API、終了コード、CIレシピ
CIからサイトを監査する実用的なレシピ:3つのRESTエンドポイント、ビルドゲートが読み取れるスコアフィールド、実際の回帰とインフラのブリップの違い。
視認性を最も失う技術的回帰はコードレビューでは見えない。 誤ったホストを指すcanonicalタグ、自己参照を失うhreflangブロック、リバースプロキシ設定から削除されたContent-Security-Policyヘッダー、ハイドレーションの背後にコンテンツを移動させるルート―それらのすべては見た目が良く、すべてのテストに合格するdiffに乗っている。 それらは数週間後に、トラフィックチャートで表面化し、原因となったコミットがスクロールアウトした後に長く経ってから現れる。
修正は構造的:誰かが思い出す日ではなく、デプロイ時に監査を実行する。 すべてのSEOReportレポートはRESTで利用可能で、レスポンスには数値スコアブロックが含まれ、シェルスクリプトでそのブロックを終了コードに変換できる。 それが全体の仕組みだ。
機械が時間通りに検出する4つの回帰クラス
人間のレビューを特にすり抜けやすい失敗モードが4つあり、具体的に名前を付ける価値がある。なぜなら、それらが実際にゲートが保護しているものだからだ。
Hreflang。 クラスターは、自己参照、リターンリンク、妥当なx-default、正しく形成されたURL、そしてインデックス可能で自分自身にcanonicalを向ける代替ページに依存する。 CMS移行でURLパターンを書き換えると、すべてのロケールでリターンリンクが壊れ、個々のロケールでは壊れているように見えない。
Canonicalization。 canonicalは存在し、リダイレクトしないインデックス可能なターゲットを指し、サイト自身のホストにとどまる必要がある。 この失敗の最悪のバージョンは、本番に配備されたステージングcanonical ― 静かにインデックスしたくないホストを指名するページだ。
Security headers。 Content-Security-Policy、HSTS、Referrer-Policy、X-Frame-Options、X-Content-Type-Options、Permissions-Policy. これらはインフラ構成にあり、アプリケーションコードではないため、プロキシやエッジ構成の変更時に消え、誰も気づかない。
Render parity。 ページは HTML レスポンスで1つのバージョンを配信し、スクリプト実行後に別のバージョンを配信することがある ― 異なるタイトル、欠落した見出し、後から挿入されるcanonical、あるいはハイドレーション後にしか存在しない本文テキスト。 私たちが公開した render-parity data が判決に達したとき、ほとんどのサイトは HTML として配信されたものに対して実質的に薄いページを提供していました。 設定ファイルに ssr: false が1つあるだけで、サイトをそのグループに移動させることができます。
すべての4つは決定論的で、すべての4つはチェックが安価で、すべての4つは疑わしいときにのみ人が検査するようなものです。
3つのエンドポイントと1つのループ
パイプラインが必要とする API の表面は小さいです。 API アクセスは有料のプランに含まれ、ベアラートークンで認証されます:ダッシュボード からキーを発行し、それを Authorization: Bearer sr__live_your_api_key として送信します。 同じキーで REST と MCP を開きます。完全な参照は the developers page にあります。
レポートを送信するには1つの 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')
CI で特に重要なそのボディに関する2つの点。
forceRerun は、API が既存のスナップショットを再利用するために存在します。ボタンをクリックする人にとっては合理的ですが、デプロイゲートにとっては誤りで、そうでなければ以前のリリースを評価してしまいます。 応答は submission.reusedSnapshot で何が起こったかを教えてくれます。 強制的な新規実行は有料アカウント機能です。該当する権限のないキーは、静かに古いデータを返す代わりに理由を示す 403 を受け取ります。
audit の範囲は URL パスから推定されます。 ベアオリジンはフルサイト監査を実行します;パス付きの URL はそのページを単独で監査します。 したがってパイプラインは毎回デプロイ時にホームページをゲートし、変更が触れた特定のルートを追加できます。追加パラメータは不要です。
その後 poll. GET /api/v1/reports/:id/ready は安価なステータスエンドポイントです — それは ready、status、stage、pollAfterMs を返し、実行が失敗した場合は 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
その exit 75 は意図的で、ほとんどの統合が誤解する部分です。
回帰と実行されなかった実行を区別する
error.code は機械可読の理由と retryable ブール値を持ちます。 DNS_FAILURE、CRAWLER_BLOCKED、REDIRECT_LOOP、TLS_ERROR、NO_CONTENT、STALE_ENGINE_VERSION は再試行不可とマークされています。再試行すると同一の回答が返ります。 そのリストのほとんどはサイト上の条件を説明しており、リダイレクトループ、TLS エラー、現在クローラーをブロックするボットルールなど、ビルド失敗に値する実際のデプロイ欠陥が含まれます。 そのセット外のコードは一時的で、再試行する価値があります。
「サイトが回帰した」と「監査が実行できなかった」を同じ赤いビルドに統合するゲートは、1か月以内に無効化されます。 それらを終了コードで分けてください:ゲートに強制させた判定には 1、判定がまったく出なかった実行には 75(従来の EX_TEMPFAIL)を使用します。 ほとんどの CI システムは、2 回目を再試行し、1 回目を人間に報告するように設定できます。
スコアブロックを読む
ready が true になると、GET /api/v1/reports/:id はレポートオブジェクトを返します。 score ブロックはゲートが読む部分です:
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}'
そのフィールドのうち 3 つがほとんどの信号を担います。
failingCriticalChecks はエンジンが重大と分類する失敗数です — インデックスからページを削除したり、クローラーからコンテンツを完全に隠したりできるものです。 最初のゲートでは、これが必要な唯一の数値であり、> 0 は初日で妥当な閾値です。
overall は合成スコアで、ラチェットとして有用です:前回のデプロイ値を保存し、新しい値が選択した許容範囲よりも大幅に下がったら失敗します。 これは単一のチェックで検出されないゆっくりとした侵食を捕捉します。
domainScores はスコアを seo、ai、performance、security、brand に分解し、各々に独自の pass、fail、warn カウントがあります。 ドメインごとの最低値は、異なるチームが異なる予算を管理できるようにします — セキュリティスコアはエッジ設定を担当する人にとって意味のあるゲートで、コンテンツチームが配備したものとは独立しています。
inconclusiveChecks は独自のルールを持つべきです:決してゲートに使用しないでください。 チェックは、エンジンが確実な結論に達できなかった場合に不確定と報告し、その結果を失敗とみなすと、誰もがゲートを無視するように訓練されます。
スコアブロックはレポートのサマリーの一部であるため、ビルドゲートは完全な調査結果を取得せずにそれを読み取れます。 完全なペイロードをアーカイブしたい場合、GET /api/v1/reports/:id/result は完全な結果を返し、GET /api/v1/reports/:id/download?format=json は同じオブジェクトをダウンロード可能なアーティファクトとして返します — これをビルドに添付すると、2つのデプロイ間の差分を後で検査できるようになります。
ワークフローへの組み込み
上記のいずれも CI ベンダー固有ではありません。 GitHub Actions では、ゲート全体が既に持っているスクリプトを呼び出す1つのステップです:
- name: SEO audit gateenv:SEOREPORT_API_KEY: ${{ secrets.SEOREPORT_API_KEY }}AUDIT_URL: https://example.comrun: ./scripts/seo-gate.sh
seo-gate.sh が提出、ポーリング、そして決定で終了します:
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"
デプロイが完了した後に実行し、プレビュー環境に対してではなく。 プレビュー URL は通常、基本認証またはボットチャレンジの背後にあり、ページを取得できない監査はスコアではなく CRAWLER_BLOCKED を報告します。 実際のオリジンに対するポストデプロイは、より単純で、クローラーが経験するものに近いです。
隣接する2つの形状を知っておく価値があります。 あなたのオーケストレーションがすでにスクレイピングプラットフォーム上にある場合、our Apify actor はこの接着剤なしでスケジュールに従って同じエンジンを実行します。 エージェントの場合、同じベアラーキーが https://seoreport.dev/mcp で MCP 接続を認証し、レポートツールは connect で広告されます — トラフィック低下を調査するエージェントは監査を実行し、発見結果を自ら読むことができ、これは REST の表面とともに the developers page に記載されています。
ゲートがカバーしないこと
デプロイゲートは1つの質問に答えます:このリリースは回帰を導入しましたか。 デプロイなしで変化するすべてのことについては黙ります — 期限切れの証明書、ダッシュボードで編集された CDN 設定、レンダリングをブロックし始めるサードパーティスクリプト、あなたの正規表現と衝突する競合他社の移行など。 ドメイン単位の監視はゲートの常時稼働補完です: SEO monitoring API は同じアカウントと同じキーで登録済みドメインを毎週再監査します。
狭いバージョンから始めます。 1つのエンドポイント、1つのフィールド、failingCriticalChecks > 0、そして監査が全く実行できなかったときの正直な exit 75 です。 年に2回発火し、両方の回で信頼されるゲートは、誰もがスキップするようになった包括的なものより価値があります。