公開直後だけ404になる問題の切り分け — 静的サイトの反映待ちと未公開先行リンクを混同しない手順

  • #Astro
  • #静的サイト
  • #CI/CD
  • #トラブルシューティング
  • #監視
公開直後だけ404になる問題の切り分け — 静的サイトの反映待ちと未公開先行リンクを混同しない手順

今回やったこと

静的サイトの自動公開パイプラインにおいて、公開直後の外形監視(postpublish-check)が複数記事で HTTP 404 を検知した際、原因を「デプロイ反映待ち」と「未公開下書きへの先行リンク」の2つに正しく切り分け、復旧させた手順を記録する。

公開直後の 404 は、一見するとルーティング設定やファイル配置の重大な不具合に見える。しかし、実態を精査したところ、3件の 404 のうち2件はソースファイルが本番ブランチへ到達する前のタイミング問題であり、残る1件は下書き規約に反した未公開記事への先行リンクだった。

原因が異なる2つの事象を混同したまま慌てて原稿を修正すると、修正の二度手間や不要なデプロイサイクルの浪費を招く。本稿では、コミット到達、デプロイ完了、リンク先の公開状態を順序立てて判定する切り分けフローをまとめる。

発生した事象と監視アラートの内訳

管理下の静的サイトにおいて、記事の公開バッチが走った直後の自動検査で以下の 3 件の URL が HTTP 404 としてアラートに上がった。

  • /articles/why-people-line-up-for-mystery-2026/ (HTTP 404)
  • /articles/summer-electric-bill-defense-2026/ (HTTP 404)
  • /articles/deepseek-benchmark-unverified-2026/ 内のリンク先 /articles/deepseek-price-war-2026/ (HTTP 404)

公開監視スクリプトは URL リストに対して一律に HEAD/GET リクエストを送り、200 以外が返ったものを即座に不合格として報告する。このため、報告画面のログ上では「3件とも同じ HTTP 404 障害」として一括して表示されていた。

しかし、記事の管理状態を個別に突き合わせると、実態は全く異なる性質のエラーであることが判明した。

3件のエラー内訳と根本原因

対象URL発生状況調査結果原因分類対処方法
why-people-line-up...記事本体が 404ソースファイルが作業ブランチのままで main に未マージ反映待ち(未デプロイ)ブランチ救出コミットのマージとデプロイ待ち
summer-electric-bill...記事本体が 404ソースファイルが main に未マージ反映待ち(未デプロイ)同上
deepseek-benchmark...記事末尾のリンク先が 404参照先記事が draft: true のまま本文にリンクが直書きされていた規約違反(未公開先行リンク)原稿からリンクを一時撤去

切り分けの実際:2つの異なる原因

1. ファイルが main に届いていなかった「反映待ち」

最初の2件については、まずリポジトリの main ブランチに該当する Markdown / MDX ファイルが存在しているかを git log およびファイルツリーで確認した。

# 対象ファイルが main に存在するか確認
git ls-tree HEAD src/content/articles/ | grep "why-people-line-up"

確認の結果、ファイルは作業ブランチに取り残されており、main ブランチにはコミットされていなかった。その後、main ブランチへのマージが完了し、Cloudflare Pages のビルド・デプロイが完了した段階で、記事本文へのアクセスは自動的に HTTP 200 へ復旧した。

つまり、記事ファイル自体の記法やルーティング定義には一切の不備がなく、修正コミットを打つ必要は全くなかった。必要なのは「コミットが反映され、デプロイが完了するのを待つこと」だけだった。

2. 未公開記事へ先行して張られた「規約違反リンク」

一方、3件目の deepseek-benchmark-unverified-2026 は記事本体は正常に 200 を返していたが、記事末尾のシリーズ案内で 404 が発生していた。

<!-- 修正前の記事末尾 -->
*本記事はDeepSeek論争の検証シリーズです。
「557万ドル(約8億7,000万円)が何の値段だったか」は[第1回](/articles/deepseek-training-cost-misread-2026/)
「その後の価格競争で何が起きたか」は[価格戦争編](/articles/deepseek-price-war-2026/)で扱っています。*

リンク先の /articles/deepseek-price-war-2026/ を調べると、該当ファイルはリポジトリ内に存在していたものの、frontmatter のステータスが draft: true のままであり、本番サイトでは静的HTMLとしてビルドされていなかった。

編集方針において「未公開記事への先行リンクは禁止し、公開後に追記する」と定められているにもかかわらず、執筆時にシリーズの全体像を案内しようとして未公開の下書きURLを先行して書き込んでしまったことが直接の原因だった。

この件については、価格戦争編への言及とリンクを一旦削除し、公開済みの第1回へのリンクのみを残す修正を実施した。

<!-- 修正後の記事末尾 -->
*本記事はDeepSeek論争の検証シリーズです。
「557万ドル(約8億7,000万円)が何の値段だったか」は[第1回](/articles/deepseek-training-cost-misread-2026/)で扱っています。*

その後、価格戦争編が正式に公開されたタイミングで、改めてシリーズ案内のリンクを復元している。

静的サイトの404を切り分ける3段階手順

今回の経験から、公開直後に 404 が発生した際は、慌ててコードを編集する前に以下の3ステップで機械的に切り分けるルールを定めた。

[監視が 404 を検知]


[ステップ1: mainブランチの確認]
  ├─ ファイルが存在しない / 未マージ ───→ マージとデプロイパイプラインの完了を待つ(原稿は触らない)
  └─ ファイルが存在する


[ステップ2: ビルド・デプロイ状態の確認]
  ├─ デプロイ中 / ビルド失敗 ──────────→ CIログを確認しビルドを通す
  └─ デプロイ完了済み(実測 200) ────→ キャッシュ伝播の待機


[ステップ3: 記事内リンクの検証]
  ├─ リンク先が外部URL ───────────────→ リンク切れ・URLタイポの修正
  └─ リンク先が内部記事 ───────────────→ リンク先が `draft: true` でないか確認
                                         未公開ならリンクを外す(公開後に再配線)

ステップ1: ファイルのコミット到達を確認する

404 が出たページのソースファイルが、現在の本番配信元ブランチ(main)に存在するかを確認する。ファイル自体が存在しない場合、記事側の修正ではなく git のマージやプッシュ漏れが疑われる。

ステップ2: CI/CD のデプロイ完了と本番レスポンスを実測する

ソースが存在する場合、ホスティング環境(Cloudflare Pages や Vercel 等)のビルドが正常終了しているかを確認する。ビルド直後はエッジキャッシュへの伝播待ちで数秒〜数十秒のタイムラグが生じることがあるため、curl -I などで直接ヘッダーを確認する。

ステップ3: 記事内リンクの公開ステータスを確認する

記事自体は表示できるが記事内リンク先が 404 の場合、リンク先がサイト内の別記事であればその記事の frontmatter を確認する。未公開の下書き(draft: true)に対して先行して内部リンクを張っていないかを監査する。静的サイトジェネレーターは下書き記事のHTMLを出力しないため、リンク先は確実に 404 となる。

運用してみてわかったこと

自動監視システムが検知する「HTTP 404」というステータスコードは、現象としての結果を示しているにすぎない。

  • ソースコードがまだ main に入っていない(パイプラインの手前側の遅延)
  • ビルド成果物がエッジに反映されていない(インフラ側の伝播遅延)
  • 記事内で未公開記事をリンクしている(コンテンツ側の論理エラー)

これらはすべて外部からは同じ「404 Not Found」として観測される。監視の赤字を見て反射的に記事マークダウンを開いて書き直そうとすると、デプロイ完了を待つだけで直るものに対して不要な空コミットを重ねたり、根本的な下書き規約違反を見落としたりする。

監視アラートが出たときこそ、パイプラインのどの階層で何が起きているかを1段階ずつ確認する切り分けの型が重要になる。

更新履歴

  • 2026-09-05: 初版起稿。公開直後の404切り分け手順と未公開先行リンク対処事例を記録。