ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-06-14上級

エッジキャッシュが Next.js の空ページを固定化していた原因と、cache-worker のガード設計

ユーザーから「時々読み込みエラーになる」と報告されるのに手元では再現しない。SSR の例外が HTTP 200 で配信され、それをエッジキャッシュが固定化していた原因を Antigravity のエージェントで切り分け、cache-worker にキャッシュ拒否ガードを実装した記録です。

antigravity346cloudflare6nextjs4edge-cache2ssr

プレミアム記事

「サイトを開くと、たまに『読み込みエラーが発生しました』と出ます。リロードすると直るのですが」——個人開発で運用している技術ブログ群で、こうした報告がぽつぽつ届くようになりました。やっかいだったのは、自分の手元では一度も再現しなかったことです。何十回リロードしても正常に表示されます。アクセスログを見ても 5xx はほとんど立っていません。

結論から申し上げると、原因は「Next.js が SSR の途中で起こした例外を HTTP 200 のまま配信し、それを Cloudflare のエッジキャッシュが数時間固定化していた」ことでした。200 で返るので監視にも引っかからず、キャッシュされるので一部の利用者だけが壊れたページを踏み続けるという、観測しづらい組み合わせです。切り分けの過程と、再発させないために cache-worker へ入れたガードのコードを、以下に残しておきます。

200 で返るエラーページという盲点

まず前提として、Next.js の App Router は本文をストリーミングで送り出します。ヘッダ(ステータスコード)はボディより先にフラッシュされるため、200 OK を送った後に React のレンダリング中で例外が起きても、もうステータスコードは書き換えられません。代わりに error.tsx のエラー UI がボディの続きとして流れていきます。

つまり利用者から見れば「読み込みエラーが発生しました」という画面なのに、HTTP のステータスは 200 OK。これが第一の盲点でした。5xx を数えるダッシュボードでは異常がまったく見えません。

もう一つの発生経路がありました。記事本文は getCloudflareContext().env.ASSETS.fetch() 経由で静的 HTML を読み込む構成にしているのですが、デプロイが切り替わる一瞬、この ASSETS への取得が空振りすることがあります。すると例外こそ出ないものの、本文が空のまま記事ページが 200 で生成されてしまいます。読み込みエラー画面と空本文ページ、症状は違いますが「壊れた 200」という点で同じ穴に落ちていました。

なぜエッジキャッシュが事態を悪化させたのか

ここに Cloudflare Workers の cache-worker を重ねると、問題が増幅します。当時の cache-worker は素朴で、200 の HTML をほぼ無条件に 4 時間エッジへキャッシュしていました。

// 当時の素朴な実装(問題があった版)
const res = await fetch(request);
if (res.status === 200 && isHTML(res)) {
  const toCache = res.clone();
  ctx.waitUntil(cache.put(request, toCache)); // ← 200 なら中身を見ずに保存
}
return res;

デプロイの切り替わりは 1 日に十数回起こります。その一瞬に生成された「壊れた 200」が運悪くキャッシュへ入ると、以降 4 時間、同じエッジロケーションを使う利用者にだけ壊れたページが配信され続けます。私の手元で再現しなかったのは、別ロケーションの健全なキャッシュを引いていたからでした。「リロードすると直る」という報告も、キャッシュのばらつきとデプロイ間隔を考えると筋が通ります。

公式ドキュメントには「200 はキャッシュ可能」と書かれていますが、ストリーミング SSR では「ステータス 200」と「中身が正常」は別物です。ここを同一視していたのが設計上の誤りでした。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
「時々読み込みエラーになる」と報告されても手元で再現できなかった人が、SSR ストリーミング例外が HTTP 200 のまま配信される仕組みを理解し、原因を特定できるようになります
cache-worker でエラーマーカー・本文欠落・</html> 欠落を検出してキャッシュを拒否する 30 行ほどのガードを実装でき、壊れたページがエッジに固定化されるのを防げます
デプロイ遷移の瞬間に生成される空ページを、ASSETS の 1 回リトライと 5xx の no-store ヘッダで抑える設計判断を、自分の Cloudflare Workers プロジェクトに応用できます
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

アプリ開発2026-05-02
Antigravity に Server Actions を書かせるときの落とし穴 — Zod・revalidate・セキュリティを後付けしない実装フロー
Antigravity の AI に Next.js の Server Actions を書かせると、バリデーション・revalidate・認可チェックがごっそり抜け落ちることがあります。それを後付けにせず、最初から安全な実装に着地させるための実践フローをまとめました。
アプリ開発2026-04-30
Antigravity × Better Auth でモダンTypeScript認証基盤を構築する
NextAuth疲れに終止符を打つBetter AuthをAntigravityで導入する実践ガイド。スキーマ自動生成・OAuth・RBAC・Passkeyまで本番運用パターンを網羅します。
アプリ開発2026-04-29
Antigravity × Cloudflare Vectorize:エッジで動く本番 RAG パイプラインを構築する
Cloudflare Vectorize を使ったエッジ RAG パイプラインを Antigravity で構築する本番ガイド。チャンク分割・ハイブリッド検索・コスト設計・観測まで個人開発で運用できる粒度で解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →