存在しないURLが200を返す — 静的サイトのsoft 404を二重に検知する
今回やったこと / この記事について
静的サイトを移行した後、存在しないURLへアクセスしてもトップページがHTTP 200を返すことがある。この状態はsoft 404と呼ばれ、ステータスコードだけを見る公開後チェックを無効にする。本稿では、404ページをビルド成果物へ含める対処と、検査側でcatch-all応答を指紋化する二重化を、リポジトリ内の実装記録に沿って整理する。
対象は、AstroやCloudflare Pagesのような静的配信を使い、画像や内部リンクを自動検査している運用者である。実装ステータスは実装済み。検査対象には、記事本体、画像、内部リンクを含める。
HTTP 200だけでは「ページがある」と言えない
公開後CIで、記事URL・画像URL・内部リンクをHTTPステータスで確認しているとする。存在しないURLが404を返すなら、404を異常として扱えばよい。しかし404ページがビルド成果物に無い配信環境では、存在しないパスにもトップページのHTMLが返る。その結果は次のようになる。
| 検査対象 | 実体がある場合 | 実体が無い場合のcatch-all | ステータスだけの判定 |
|---|---|---|---|
| 記事 | 200 / 記事HTML | 200 / トップHTML | どちらも正常に見える |
| 画像 | 200 / image系 | 200 / HTML | どちらも正常に見える |
| 内部リンク | 200 / 対象ページ | 200 / トップHTML | どちらも正常に見える |
この状態では、featured画像が欠落していても、内部リンクが切れていても、検査が一度も警告しない。緑色の結果は「対象が存在する」証拠ではなく、「サーバーが何かを返した」証拠に変わってしまう。
配信側に本物の404を用意する
第一の対処は、サイト側に404ページを追加することだった。src/pages/404.astro では、ページが見つからないことを示す文言と、ホーム・記事一覧・ボードへのリンクを用意している。Astroのビルドに404ページが含まれることで、配信側が不在パスを不在として返せる土台ができる。
<Layout title="ページが見つかりません" noIndex={true}>
<section class="not-found">
<p class="code">404</p>
<h1>ページが見つかりません</h1>
</section>
</Layout>
404ページを追加するだけで、配信経路の意図は明確になる。ただし、ビルド設定や配信側の挙動が将来変わる可能性は残る。検査側も、ステータスコードだけに依存させない。
検査側で不在応答の指紋を取る
検査開始時に、存在しないプローブURLへGETする。応答が200なら、本文全体のSHA-256と<title>を保存する。その後の各URLが200を返しても、本文ハッシュまたはtitleがプローブと一致したらcatch-allと判定する。
probe = get(f"{base}/__postpub_probe_404__/")
catchall = fingerprint(probe) if probe.code == 200 else None
def missing(code, body):
if code != 200:
return True
if catchall is None:
return False
return fingerprint(body) == catchall
画像はさらにContent-Typeを確認する。HTTP 200でもimage/*でなければ画像の実体ではない。内部リンクは、catch-allの本文指紋と照合する。これにより、ステータスと応答内容の二つの観測を組み合わせられる。
検査を強くしすぎないための境界
ネットワーク障害や403、405を、存在しないページと同じERRORにすると、正しい記事まで公開後処理が止まる。明確に存在しないと判断できる404・410と、catch-all指紋の一致を不在判定の中心に置く。検査スクリプトが異常終了した場合は、結果が分からないため公開処理へ進めない。
この境界は「検査を緩める」ためではない。壊れていないものをネットワーク一時障害で壊れたと判定しないための分類である。逆に、ローカル画像の欠落や内部リンクの破損のようにリポジトリ内で確定できる問題は、明確なエラーとして扱う。
やってみてわかったこと
公開後チェックの品質は、チェック項目の数より「検査対象が実体だったか」を確認できるかで決まる。404ページを配備し、存在しないプローブの応答を基準にし、画像にはContent-Typeまで要求する。この3点で、200を返すだけのcatch-allを成功と誤認しにくくなる。
静的サイト移行では、ページを表示できることと、存在しないページを正しく不在として返すことは別の要件である。公開後に見つかった欠損を修正するには手戻りが大きい。配信側の404と検査側の指紋判定を同じ変更として扱うのが安全である。
更新履歴
- 2026-08-19: 初稿。404ページとcatch-all指紋の二重検知を整理。
訂正履歴
- なし。