CLAUDE LABJP
BILLING — The Jun 15 change that would have moved Agent SDK, headless runs, GitHub Actions, and third-party agents to separate monthly credits has been pulled; that usage stays within your subscription limitsMANAGED — Code w/ Claude introduced Managed Agents that run in a sandbox you control and connect to your private MCP servers, keeping both execution and reachable services inside enterprise boundariesLIMITS — The same conference doubled Claude Code rate limits and raised API limits, giving multi-stage agent workflows more headroomSUBAGENTS — Claude Code adds nested sub-agents that can spawn their own agents, plus a safe mode that isolates broken configurationsEXPORT — Fable 5 and Mythos 5 remain suspended under a US export-control directive (since Jun 12); every other model including Opus, Sonnet, and Haiku runs normallyCODE — Claude Code keeps shipping updates: improvements to /doctor, Remote Control, and /bug, plus expanded fallback modelsBILLING — The Jun 15 change that would have moved Agent SDK, headless runs, GitHub Actions, and third-party agents to separate monthly credits has been pulled; that usage stays within your subscription limitsMANAGED — Code w/ Claude introduced Managed Agents that run in a sandbox you control and connect to your private MCP servers, keeping both execution and reachable services inside enterprise boundariesLIMITS — The same conference doubled Claude Code rate limits and raised API limits, giving multi-stage agent workflows more headroomSUBAGENTS — Claude Code adds nested sub-agents that can spawn their own agents, plus a safe mode that isolates broken configurationsEXPORT — Fable 5 and Mythos 5 remain suspended under a US export-control directive (since Jun 12); every other model including Opus, Sonnet, and Haiku runs normallyCODE — Claude Code keeps shipping updates: improvements to /doctor, Remote Control, and /bug, plus expanded fallback models
Articles/Claude Code
Claude Code/2026-06-17Intermediate

It Says '500' in the Browser but curl Returns 200 — When a Next.js Error Boundary Misreads a ChunkLoadError

A production Next.js article page shows '500 Internal Server Error' in the browser, yet curl returns 200. The culprit was not the server but a ChunkLoadError surfacing through error.tsx. Here is the diagnosis and a fix with built-in auto-reload.

Next.js7Cloudflare Workers12ChunkLoadErrortroubleshooting85error boundary

Late one night I opened an article on my own site and got "500 Internal Server Error."

I reloaded, and it rendered as if nothing had happened.

A few minutes later, a different article showed the same screen. That unsettled me. If the server were truly down, it would not fail this capriciously.

As an indie developer I run four Next.js sites on Cloudflare Workers, and I deploy many times a day. For a while I assumed this "sometimes 500, fine on reload" behavior was a caching problem. The short version: the server never returned a 500 at all.

First, question whether it is really a 500

Before trusting what the browser paints, check the HTTP response itself. Skip this and you will spend hours chasing a server error that does not exist.

Pull just the status code for the same URL.

# Browser says "500" — what is the real status?
curl -s -o /dev/null -w "%{http_code}\n" \
  https://claudelab.net/articles/claude-code/your-article-slug
 
# Hit it ten times to look for flapping
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

Every one of the ten responses came back 200.

Malformed cookies, an absurdly long slug, RSC headers — none of it changed anything. The server stayed at 200. The "500," then, was not a server response. It was a picture drawn by something happening inside the browser.

The culprit was a ChunkLoadError

I opened the browser console and captured the logs at the moment the 500 screen appeared. There it was: ChunkLoadError, repeated.

Here is the mechanism. Next.js ships JavaScript as small, hash-named chunks (files like framework-abc123.js). When a new deploy goes out, those file names change.

But the user's browser is still holding the old page. When that page reaches for "the chunk that existed a moment ago," the server has already swapped it for a new name, so it 404s. The JavaScript that failed to load throws, and Next.js fires its client-side error boundary (error.tsx).

At this point the server is clearly off the hook. The more often you deploy, the more moments arise where an old tab collides with a fresh deploy. On my schedule, with several deploys a day, someone was hitting that window every single day.

Why it said "500"

Opening src/app/[locale]/error.tsx made it click.

// Old, problematic version: it branded every client error as "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>A server error occurred.</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}

error.tsx renders for any client-side exception — a ChunkLoadError, a hydration mismatch, anything. Yet the heading hard-coded "500 Internal Server Error," so failures that had nothing to do with the server still looked like a server 500.

Worse, reset() only attempts to remount the component; it cannot re-fetch a chunk that already failed to load. That is why "Try again" did nothing, and only a manual reload recovered the page.

Build in an auto-reload so it stops misreporting

The fix has three parts. Since most ChunkLoadErrors clear once the browser fetches the new chunks, reload once automatically on first error. Then drop "500" from the copy, and leave a clue in the console.

'use client';
 
import { useEffect } from 'react';
 
export default function Error({ error, reset }: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Always record the error name and digest for diagnosis
    console.error('[error boundary]', error.name, error.message, error.digest);
 
    // ChunkLoadError comes from stale chunk references — reload once to recover transparently
    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>Something went wrong while loading</h1>
      <p>The page failed to load. A reload often recovers it.</p>
      <button onClick={() => {
        sessionStorage.removeItem('cl_chunk_reloaded');
        window.location.reload();
      }}>
        Reload the page
      </button>
    </div>
  );
}

The sessionStorage flag is the key detail. Without it, if the same error returns after the reload you fall into an infinite reload loop. Mark the page once it auto-reloads, and if it still fails, hand off quietly to the manual button.

sessionStorage can throw in private browsing, so wrap it in try/catch for production. I do not call reset() here, because it has no effect on a ChunkLoadError.

Do not bake the error screen into the edge cache

There is one more easy thing to miss. If you cache HTML at an edge like Cloudflare, you risk freezing a broken response generated during the split-second of a deploy transition.

In my setup I added a guard so the cache layer refuses to store HTML that carries traces of an error: HTML containing the error-boundary marker (something like data-error-boundary), HTML missing its closing </html>, and article HTML with an empty body are all excluded from caching.

That also closes the path by which a broken page could be served to Googlebot. For crawl health, freezing an error screen was exactly the behavior I wanted to avoid. My broader Cloudflare setup is written up in operational notes for running Next.js on Cloudflare Workers.

A checking order so you avoid the same trap

When the symptom is "sometimes 500, fine on reload," start with curl and confirm the status code. If it returns 200, the server is off the suspect list right there.

Next, look for ChunkLoadError in the browser console. If it is there, the culprit is the mismatch between a deploy and chunk names. Then check whether error.tsx still hard-codes "500," and whether an auto-reload is in place.

I took the display at face value and burned my first few hours on a cache investigation. Simply remembering that the error code on screen and the actual HTTP status are two different things makes the diagnosis far faster.

Relatedly, telling network issues apart is covered in diagnosing Claude Code network connection errors, and the Workers bundle design lives in dodging the 62 MiB limit with a Content Split Architecture.

I hope this gives you a faster first step if the same capricious "500" has been troubling you too.

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Claude Code2026-04-21
Running Next.js on Cloudflare Workers in Production with Claude Code — Every Build Crisis, Cache Bug, and Automation Pattern We Solved
Running Next.js on Cloudflare Workers is not the same as Vercel. The 62 MiB bundle limit, ASSETS binding quirks, and edge cache personalization conflicts are real production hazards. Here's how Claude Code helped us solve each one.
Claude Code2026-05-28
The Morning I Hit Cloudflare Workers' 62 MiB Limit, and the Content Split Architecture I Rebuilt for 5,000 Articles
One morning, an indie developer running 4 AI tech blogs on Cloudflare Workers + Next.js woke up to a deployment that had stopped because articles.json grew large enough to push the Worker bundle past the 62 MiB limit. This is the concrete implementation walkthrough of splitting metadata from HTML, with the build-time and bundle-size numbers observed across the first month.
Claude Code2026-04-06
Claude Code × Next.js 15 App Router Production: RSC, Server Actions, Auth, Testing & Deployment
The practical guide to production Next.js 15 App Router development with Claude Code. Covers RSC architecture decisions, Server Actions patterns, Auth.js v5, Vitest testing, and Cloudflare Workers deployment with practical code examples.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →