Next.js

Fix ChunkLoadError: Loading chunk failed (Next.js, webpack)

"ChunkLoadError: Loading chunk 5760 failed" almost always means a user has an old tab open after you shipped a new deploy. The fix is configuration — deploymentId, build IDs, and CDN headers — not a try/catch.

Mahdi Ben RhoumaMahdi Ben Rhouma
Updated
9 min read

You shipped a deploy. Minutes later your error tracker lights up:

text
ChunkLoadError: Loading chunk 5760 failed.
(error: https://yourapp.com/_next/static/chunks/5760-40c839657ea31a15.js)

Or the timeout variant:

text
ChunkLoadError: Loading chunk app/layout failed.
(timeout: https://yourapp.com/_next/static/chunks/app/layout.js)

Those two are webpack's wording. A Next.js 16 app built with Turbopack, the default bundler, reports the same failure differently. We reproduced it on 1 October 2026 (Next.js 16.3.8, next start, headless Chromium): load the page, delete the chunk files it has not fetched yet to simulate a newer deploy, then trigger a next/dynamic import:

text
Turbopack build:  ChunkLoadError | Failed to load chunk /_next/static/chunks/1fxg5mnf5zpy6.js from module 89073
webpack build:    ChunkLoadError | Loading chunk 9 failed.
                  (error: http://localhost:3010/_next/static/chunks/9.bc01776b4de22c3d.js)

The error name is ChunkLoadError in both cases, so a filter on the name catches both; a filter on the "Loading chunk" text misses every Turbopack build.

The instinct is to wrap something in a try/catch. Don't — this is almost never a code bug. It is version skew: a browser is asking for a JavaScript chunk that your latest deploy already deleted. This guide explains the real causes and the official Next.js configuration that fixes each one.

What the two webpack variants actually mean#

In a webpack build, the parenthetical in the message tells you which failure mode you hit (the Turbopack message above has no such suffix):

  • (error: <url>) — the request for the chunk completed but failed, typically a 404. The file no longer exists at that hashed path.
  • (timeout: <url>) — the request never completed within webpack's chunk-load timeout (default 120000 ms, set via output.chunkLoadTimeout). This is the network/slow-connection branch.

Knowing which one you have points you at the right fix below.

Root cause #1: stale chunks after a new deploy (the common one)#

Next.js fingerprints static assets with a content hash in the filename (5760-40c839657ea31a15.js). When you deploy, the bundle changes, the hashes change, and the old files are gone. Any client still running the previous version — an open tab, a bfcache restore, a CDN serving stale HTML — requests the old chunk path and gets a 404.

Next.js documents this exact failure mode under self-hosting and calls it version skew: "Missing assets: The client requests JavaScript or CSS files that no longer exist on the server." Rolling deployments make it worse, because some instances serve new assets while others still serve old ones.

The fix: set deploymentId

Give every build a stable deployment identifier. Next.js appends it (?dpl=…) to asset URLs and exposes it via response headers; when the client detects a mismatch between its deployment ID and the server's, it triggers a hard navigation (full page reload) instead of a broken client-side navigation.

js
// next.config.js
module.exports = {
  deploymentId: process.env.DEPLOYMENT_VERSION || process.env.GIT_SHA,
}

This is the official, intended mechanism for this cause, with one limit the docs spell out: the check runs on navigation requests, and "A per-deployment value only avoids skew if requests are also routed by deployment. Next.js does not route on ?dpl=, so that routing comes from your host or CDN. Without it, clients that reach an instance from another deployment during a rollout will reload rather than navigate." See the deploymentId config and self-hosting version skew docs.

Version caveat

deploymentId shipped as experimental.deploymentId in v13.4.10 and was promoted to a top-level config option in v14.1.4. Before 14.1.4, use the experimental. form.

Root cause #2: each container runs its own build#

Chunk file names come from content hashes, not from the build ID, but the build ID is how Next.js identifies which version is being served. The generateBuildId docs say: "The same build should be used and boot up multiple containers." Build once and ship that output to every instance. If you have to rebuild for each stage, pin the build ID:

js
// next.config.js
module.exports = {
  generateBuildId: async () => process.env.GIT_HASH,
}

Do not combine it with deploymentId expecting both to work: the same page notes that "When deploymentId is set, Next.js uses a constant build ID and generateBuildId has no effect." (the deploymentId version history dates that behaviour to v16.2.0).

Root cause #3: self-hosted output: 'standalone' builds#

If you self-host with output: 'standalone' (common in Docker), the minimal server does not copy public or .next/static by default. Every chunk then 404s until you copy them. Straight from the output docs:

bash
cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/

Root cause #4: a CDN or proxy serving stale HTML#

Next.js sets Cache-Control: public, max-age=31536000, immutable on hashed static assets — they are safe to cache forever because the hash changes when the content does. The problem appears when a CDN, reverse proxy, or service worker caches the HTML (which references the hashed chunks) past its life, or rewrites/misroutes /_next/static/ requests.

Don't
CDN caches the HTML document for hours, so it keeps pointing browsers at chunk hashes from a deploy you already replaced.
Do
Let the immutable headers on /_next/static stand, keep HTML short-lived, and never strip or redirect /_next/static/ at the proxy. Use assetPrefix if assets live on a separate domain.

If you serve assets from a separate domain/CDN, configure assetPrefix so chunk URLs are generated correctly.

Root cause #5: network failure / timeout (the (timeout:) variant)#

A flaky connection, an aggressive ad-blocker, or a slow CDN can make the chunk request hang past webpack's limit. That surfaces as the (timeout:) form. The relevant knob is webpack's output.chunkLoadTimeout (default 120000 ms), reachable through a webpack override in next.config. Treat this as a webpack-level setting, not a Next.js flag, and it only applies to next build --webpack: Turbopack does not read a webpack() function, and in Next.js 16 a plain next build fails on purpose while one is present (see the version 16 upgrade guide).

A safety net for dynamic imports#

When you load a component with next/dynamic or React.lazy, React's docs are explicit: "If the Promise rejects, React will throw the rejection reason for the nearest Error Boundary to handle." So a failed chunk load is catchable by an error boundary — you can render a "please refresh" fallback.

Official vs community

The error-boundary catch is documented (see React lazy). The widely-shared "catch ChunkLoadError → reload once via sessionStorage guard" pattern is a community technique, not official guidance. It is a reasonable last-resort UX layer, but it does not address the root cause — ship deploymentId first.

When this won't work
  • The chunk genuinely fails to parse in an old browser (a real SyntaxError, not version skew) — that is a code/transpile-target issue, not a deploy issue.
  • You only see it locally in dev — clear .next and restart; dev chunking differs from production.
  • Your import() path is actually wrong or the module throws at module scope — fix the import, an error boundary only masks it.

Check your own headers#

Root causes #1 and #4 leave a trace in the HTTP headers: the HTML of the page and the chunk it asks for. Run curl -I on both — the page URL, then one /_next/static/chunks/... file from the failing request — and paste the output below. The checker reads the caching and status headers locally in your browser (nothing is sent anywhere) and tells you whether stale HTML, a CDN cache hit or a rewrite is behind the error, with the fix for your host.

Runs entirely in your browser — nothing you paste is sent anywhere. Cookie values, Authorization and token-like URL parameters (query and fragment) are masked in the output.

curl -sI https://your-site.com/some-page

curl -sI https://your-site.com/_next/static/chunks/…js

Take the chunk URL from the error itself (Loading chunk 5760 failed. (error: https://…)) or from DevTools → Network, filtered on _next/static. curl -v and curl -L output work too.

Decision guide#

SymptomMost likely causeFirst fix
Spikes right after each deployVersion skew (#1)deploymentId
Only on multi-instance/rolling deploysEach container built separately (#2)one build for every instance
Self-hosted Docker, 404 on every chunkStandalone missing assets (#3)copy .next/static + public
Behind Cloudflare/proxy, intermittentStale HTML cache (#4)fix CDN/HTML cache headers
(timeout:) in the messageNetwork/slow load (#5)check connectivity / chunkLoadTimeout

Further reading: Fix error:0308010C digital envelope routines::unsupported. Also see Fix UnhandledSchemeError: Reading from "node:buffer", Fix Failed to fetch dynamically imported module (Vite).

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.