◉CLAUDE LABEN
●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/Claude Code
⟐ Claude Code/2026-06-17中級

デプロイ直後に「500」と出るのに curl は 200 を返す — Next.js のエラー境界が ChunkLoadError を取り違えていた話

本番のNext.js記事ページがブラウザで「500 Internal Server Error」と表示されるのにcurlでは200。原因はサーバーではなくChunkLoadErrorとerror.tsxの誤表示でした。切り分け手順と自動リロードを組み込んだ修正を記録します。

Next.js8Cloudflare Workers16ChunkLoadErrorトラブルシューティング51error boundary

夜中、自分のサイトの記事を開いたら「500 Internal Server Error」。

リロードすると、何事もなかったように表示されます。

数分後、別の記事でまた同じ画面。胸の奥がざわつきました。サーバーが落ちているなら、こんなに気まぐれな出方はしないはずです。

個人開発で、Dolice Labs として4つのNext.jsサイトをCloudflare Workers上で運用しています。デプロイは1日に何度も走ります。この「ときどき500、リロードで回復」という症状を、私自身しばらくキャッシュの問題だと思い込んでいました。結論から言うと、サーバーは一度も500を返していませんでした。

同じ環境で同じ症状に何度も出会ったからこそ、表示と実態のずれにようやく気づけました。再現性の低いエラーほど、思い込みで原因を決めつけてしまいがちです。

まず「本当に500なのか」を疑う

ブラウザの表示を信じる前に、HTTPレスポンスそのものを確認します。ここを飛ばすと、存在しないサーバーエラーを延々と追うことになります。

同じURLに対して、ステータスコードだけを取り出します。

# 表示は「500」だが、実際のステータスは?
curl -s -o /dev/null -w "%{http_code}\n" \
  https://claudelab.net/articles/claude-code/your-article-slug
 
# 念のため10連続で叩いて揺れを見る
for i in $(seq 1 10); do
  curl -s -o /dev/null -w "%{http_code} " \
    https://claudelab.net/articles/claude-code/your-article-slug
done; echo

返ってきたのは、10回すべて 200 でした。

不正な Cookie を付けても、長大な slug を投げても、RSC ヘッダーを付けても、サーバーは一貫して200を返します。つまり「500」はサーバーの応答ではなく、ブラウザの中で起きた何かが描いた絵だったのです。

犯人は ChunkLoadError だった

ブラウザのコンソールを開いて、500画面が出た瞬間のログを残しておきます。そこに ChunkLoadError が並んでいました。

何が起きているか。Next.js は JavaScript をハッシュ付きの細かいチャンク(framework-abc123.js のようなファイル)に分割して配信します。新しいデプロイが走ると、これらのファイル名が変わります。

ところが、ユーザーのブラウザにはまだ古いページが残っています。そのページが「さっきまであったチャンク」を取りに行くと、サーバー側ではもう別名に置き換わっていて404。読み込みに失敗したJavaScriptは例外を投げ、Next.jsのクライアント側エラー境界(error.tsx)が発火します。

ここまでで、サーバーは無関係だとわかります。デプロイ頻度が高いサイトほど、この「古いタブが新しいデプロイにぶつかる」瞬間が増えます。1日に何度もデプロイする私の運用では、毎日必ず誰かがこの瞬間を踏んでいた計算になります。

なぜ「500」と表示されたのか

src/app/[locale]/error.tsx を開いて、ようやく腑に落ちました。

// 旧実装(問題あり): あらゆるクライアントエラーを「500」と決めつけていた
'use client';
 
export default function Error({ error, reset }: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div className="error-page">
      <h1>500 Internal Server Error</h1>
      <p>サーバーでエラーが発生しました</p>
      <button onClick={reset}>再試行</button>
    </div>
  );
}

error.tsx は、ChunkLoadError でもハイドレーション不一致でも、クライアント側のあらゆる例外で表示されます。それなのに見出しに「500 Internal Server Error」と固定文言を書いていたため、サーバーとは無関係のエラーまで「サーバー500」に見えていたのです。

しかも reset() はコンポーネントの再マウントを試みるだけで、すでに失敗したチャンクの読み込みはやり直せません。だから「再試行」を押しても直らず、ユーザーが手でリロードしたときだけ回復していました。

自動リロードを組み込んで誤表示をなくす

修正の方針は3つです。ChunkLoadError の大半は新しいチャンクを取り直せば消えるので、初回エラー時に一度だけ自動リロードします。そして文言から「500」を外し、原因の手がかりをコンソールに残します。

'use client';
 
import { useEffect } from 'react';
 
export default function Error({ error, reset }: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // 原因究明のため、エラー名と digest を必ず残す
    console.error('[error boundary]', error.name, error.message, error.digest);
 
    // ChunkLoadError は古いチャンク参照が原因。一度だけ自動リロードで透過回復
    const isChunkError =
      error.name === 'ChunkLoadError' ||
      /Loading chunk [\d]+ failed/.test(error.message);
 
    if (isChunkError && typeof window !== 'undefined') {
      const KEY = 'cl_chunk_reloaded';
      if (!sessionStorage.getItem(KEY)) {
        sessionStorage.setItem(KEY, '1');
        window.location.reload();
      }
    }
  }, [error]);
 
  return (
    <div className="error-page">
      <h1>読み込みエラーが発生しました</h1>
      <p>ページの読み込みに失敗しました。再読み込みで回復することがあります。</p>
      <button onClick={() => {
        sessionStorage.removeItem('cl_chunk_reloaded');
        window.location.reload();
      }}>
        ページを再読み込み
      </button>
    </div>
  );
}

ポイントは sessionStorage のフラグです。これがないと、リロード後にまた同じエラーが出たとき無限リロードに陥ります。一度自動リロードしたら印を付け、それでも直らなければ静かに手動ボタンへ委ねます。

sessionStorage はプライベートブラウジングで例外を投げることがあるので、本番では try/catch で包んでください。reset() は ChunkLoadError には効かないため、ここでは使っていません。

エッジキャッシュにエラー画面を焼き付けない

もう一つ見落としがちな点があります。Cloudflare のようなエッジでHTMLをキャッシュしている場合、デプロイ遷移の一瞬に生成された壊れた応答を、そのまま固定化してしまう危険です。

私の環境では、キャッシュ層でエラーの痕跡を含むHTMLを保存しないようガードを足しました。具体的には、エラー境界のマーカー(data-error-boundary のような目印)を含むHTML、</html> が欠落したHTML、本文が空の記事HTMLはキャッシュ対象から外します。

これでGooglebotに壊れたページが配信される経路も塞げます。クロール健全性の観点でも、エラー画面の固定化は避けたい挙動でした。Cloudflare運用全体の設計は、Next.jsをCloudflare Workersで運用するときの実務メモに整理しています。

同じ罠を踏まないための確認順序

症状が「ときどき500、リロードで回復」だったら、まず curl でステータスコードを確かめてください。200が返るなら、その時点でサーバーは容疑から外れます。

次にブラウザのコンソールで ChunkLoadError の有無を見ます。あれば、犯人はデプロイとチャンク名の食い違いです。そして error.tsx に固定文言の「500」が残っていないか、自動リロードが入っているかを確認します。

私自身、表示を鵜呑みにして最初の数時間をキャッシュ調査に溶かしました。表示されたエラーコードと、実際のHTTPステータスは別物だと意識するだけで、切り分けは一気に速くなります。

関連して、ネットワーク起因の見分け方はClaude Code のネットワーク接続エラーの切り分けに、Workers のバンドル設計はContent Split Architecture で 62 MiB 制限を回避するにまとめてあります。

同じ「気まぐれな500」に悩んでいる方の、切り分けの一歩目になれば幸いです。

シェア

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

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

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

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⟐ Claude Code2026-08-19
一度キャッシュに載った500ページは、直しても消えません
修正をデプロイしたのに壊れたページが出続けたことがあります。原因はコードではなくエッジキャッシュでした。ステータスコードだけを見る保存判定が、200で返る壊れたHTMLを通していたのです。本文の完全性で保存可否を決めるガードの実装と、運用で調整した誤検知の線引きを、実際に踏んだ経緯とともに書きました。
⟐ Claude Code2026-10-13
「cd して git add して commit」の承認が毎回出る — 設定の抜けではなく、仕様だと分かるまでにたどった道
cd /path && git add && git commit のような複合コマンドで、許可ルールを入れても承認が繰り返し出る。公式の権限ドキュメントと GitHub の報告を突き合わせると、cd と git の組み合わせは設計上プロンプトが出ると分かりました。原因の切り分け順と、手数を減らす4つの直し方を並べます。
⟐ Claude Code2026-10-12
SPM に移したら、起動した瞬間にアプリが落ちるようになりました — dyld の一行を Claude Code と読み解いた記録
CocoaPods から Swift Package Manager へ移した直後、ビルドは通るのに起動した瞬間に落ちました。dyld の一行から原因を切り分け、直し方と確認コマンドまでをまとめます。