nextjs - ✅(Solved) Fix Turbopack: dynamic import() failures stay failed no recovery path without a full page reload [2 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#93489Fetched 2026-05-06 06:11:49
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Timeline (top)
cross-referenced ×2commented ×1issue_type_added ×1labeled ×1

Error Message

"use client";

import { useState } from "react";

export default function Page() { const [status, setStatus] = useState<string>("idle");

const onClick = async () => { setStatus("loading");

try {
  const mod = await import("../components/heavy-module");
  mod.run();
  setStatus("ok");
} catch (err) {
  setStatus(`error: ${(err as Error).message}`);
}

};

return <button onClick={onClick}>Run ({status})</button>; }

Root Cause

There is currently no built-in retry mechanism in the Turbopack runtime, and the webpack-retry-chunk-load-plugin ecosystem solution is not applicable because Turbopack does not expose a plugin system.

Fix Action

Fix / Workaround

At the moment, this is the most reliable workaround.

PR fix notes

PR #93518: fix(turbopack): allow dynamic import retry after chunk load failure

Description (problem / solution / changelog)

What?

Fixes #93489.

This updates the Turbopack browser runtime so failed dynamic chunk-load attempts do not leave rejected load state cached in a way that prevents later dynamic import() attempts from retrying.

Successful chunk loads continue to use the existing caching behavior. Failed loads rethrow the original error, but clear the failed cached state so a later user-triggered import can attempt to load the chunk again after connectivity is restored.

This intentionally does not add:

  • configurable retry
  • exponential backoff
  • runtime hooks
  • cache-busting URLs
  • webpack behavior changes

Why?

A transient chunk-load failure, such as a temporary offline/network failure, could leave rejected Promise state in Turbopack runtime caches. Later attempts to dynamically import the same chunk could reuse the failed state instead of issuing a fresh load attempt.

This meant the app could remain stuck until a full reload cleared runtime state.

How?

The runtime now evicts failed cached chunk-load entries on rejection while preserving successful and in-flight caching behavior.

The fix keeps the existing ChunkLoadError behavior intact and does not swallow or replace the original error.

Regression coverage was added in test/production/chunk-load-failure/chunk-load-failure.test.ts with a new /retry fixture. The test aborts the first async chunk request, confirms ChunkLoadError, clicks the same load button again, allows the chunk request to continue, and verifies the lazy module renders.

Verification

  • git diff --check passed.
  • Commit pre-hook / lint-staged passed.

I attempted the targeted Turbopack browser regression locally, but this checkout appears to require rebuilt Turbopack/native runtime artifacts for the browser test to exercise the edited TypeScript runtime source.

Changed files

  • test/production/chunk-load-failure/app/retry/page.tsx (added, +31/-0)
  • test/production/chunk-load-failure/chunk-load-failure.test.ts (modified, +80/-9)
  • turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/base/runtime-base.ts (modified, +27/-1)
  • turbopack/crates/turbopack-ecmascript-runtime/js/src/browser/runtime/dom/runtime-backend-dom.ts (modified, +23/-4)

PR #21: fix(turbopack): allow dynamic import retry after chunk load failure

Description (problem / solution / changelog)

Original author: @themhoeshateme

What?

Fixes #93489.

This updates the Turbopack browser runtime so failed dynamic chunk-load attempts do not leave rejected load state cached in a way that prevents later dynamic import() attempts from retrying.

Successful chunk loads continue to use the existing caching behavior. Failed loads rethrow the original error, but clear the failed cached state so a later user-triggered import can attempt to load the chunk again after connectivity is restored.

This intentionally does not add:

  • configurable retry
  • exponential backoff
  • runtime hooks
  • cache-busting URLs
  • webpack behavior changes

Why?

A transient chunk-load failure, such as a temporary offline/network failure, could leave rejected Promise state in Turbopack runtime caches. Later attempts to dynamically import the same chunk could reuse the failed state instead of issuing a fresh load attempt.

This meant the app could remain stuck until a full reload cleared runtime state.

How?

The runtime now evicts failed cached chunk-load entries on rejection while preserving successful and in-flight caching behavior.

The fix keeps the existing ChunkLoadError behavior intact and does not swallow or replace the original error.

Regression coverage was added in test/production/chunk-load-failure/chunk-load-failure.test.ts with a new /retry fixture. The test aborts the first async chunk request, confirms ChunkLoadError, clicks the same load button again, allows the chunk request to continue, and verifies the lazy module renders.

Verification

  • git diff --check passed.
  • Commit pre-hook / lint-staged passed.

I attempted the targeted Turbopack browser regression locally, but this checkout appears to require rebuilt Turbopack/native runtime artifacts for the browser test to exercise the edited TypeScript runtime source.

Changed files

  • dummy_file_pr_93518.txt (added, +1/-0)

Code Example

my-app/
  app/
    page.tsx
  components/
    heavy-module.ts

---

"use client";

import { useState } from "react";

export default function Page() {
  const [status, setStatus] = useState<string>("idle");

  const onClick = async () => {
    setStatus("loading");

    try {
      const mod = await import("../components/heavy-module");
      mod.run();
      setStatus("ok");
    } catch (err) {
      setStatus(`error: ${(err as Error).message}`);
    }
  };

  return <button onClick={onClick}>Run ({status})</button>;
}

---

export const run = () => console.log("loaded");

---

pnpm next build --turbo && pnpm next start

---

pnpm next dev --turbo

---

Failed to load chunk ...

---

Failed to load chunk ...

---

Failed to load chunk /_next/static/chunks/<hash>._.js

from module [project]/components/heavy-module.ts [app-client]
(ecmascript, async loader)

  at async Promise.all
  at async Page.onClick

---

const mod = await import("../components/heavy-module").catch(async () => {
  await new Promise((r) => setTimeout(r, 300));
  return import("../components/heavy-module");
});

---

window.location.reload();

---

next build --turbo

---

next dev --turbo

---



---

Operating System:                                                                                                                                                                                                                                                                                     
    Platform: darwin                                                                                                                                                                                                                                                                                    
    Arch: arm64                                                                                                                                                                                                                                                                                         
    Version: Darwin 25.2.0                                                                                                                                                                                                                                                                              
    Available memory (MB): 24576                                                                                                                                                                                                                                                                        
    Available CPU cores: 10                                                                                                                                                                                                                                                                             
  Binaries:                                                                                                                                                                                                                                                                                             
    Node: 24.14.1                                                                                                                                                                                                                                                                                       
    npm: 11.12.1                                                                                                                                                                                                                                                                                        
    pnpm: 10.28.0
  Relevant Packages:                                                                                                                                                                                                                                                                                    
    next: 15.5.14
    react: 18.3.1                                                                                                                                                                                                                                                                                       
    react-dom: 18.3.1                                                                                                                                                                                                                                                                                 
    typescript: 5.8.3
  Next.js Config:                                                                                                                                                                                                                                                                                       
    output: N/A
  Build command: next build --turbo                                                                                                                                                                                                                                                                     
  Dev command: next dev --turbo
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/kumarajay0412/turbopack-chunk-load-repro

To Reproduce

Turbopack dynamic import() failures persist after transient network errors with no built-in retry

When a dynamic import() rejects (e.g. user briefly offline, transient 5xx, network blip), the failure persists across subsequent import() calls for the same chunk for a meaningful window of time, even after connectivity is fully restored.

The only reliable recovery is a hard page reload.

There is currently no built-in retry mechanism in the Turbopack runtime, and the webpack-retry-chunk-load-plugin ecosystem solution is not applicable because Turbopack does not expose a plugin system.

This makes UI flows that trigger dynamic imports in response to user interaction (lazy-loaded heavy modules behind a button click) brittle on flaky networks.


Reproduction

Minimal Next.js 15 app.

Both next dev --turbo and next build --turbo reproduce the issue.

Project structure

my-app/
  app/
    page.tsx
  components/
    heavy-module.ts

app/page.tsx

"use client";

import { useState } from "react";

export default function Page() {
  const [status, setStatus] = useState<string>("idle");

  const onClick = async () => {
    setStatus("loading");

    try {
      const mod = await import("../components/heavy-module");
      mod.run();
      setStatus("ok");
    } catch (err) {
      setStatus(`error: ${(err as Error).message}`);
    }
  };

  return <button onClick={onClick}>Run ({status})</button>;
}

components/heavy-module.ts

export const run = () => console.log("loaded");

Steps to reproduce

  1. Run:

    pnpm next build --turbo && pnpm next start

    or:

    pnpm next dev --turbo
  2. Open the app, but do not click the button yet.

  3. In DevTools → Network, switch to Offline.

  4. Click the button.

    The dynamic import rejects with:

    Failed to load chunk ...

    which is expected.

  5. Switch DevTools back to Online.

  6. Click the button again multiple times within ~5 seconds.


Expected behavior

Once connectivity is restored, the next click should successfully load the chunk.


Actual behavior

Subsequent clicks continue to fail with the same:

Failed to load chunk ...

error for several seconds, even though the browser is fully online again.

Eventually one click succeeds, but only after a delay long enough that many users would assume the app is broken and refresh the page.

If a service worker is registered, or the browser places the failed request into HTTP negative cache, the failure can persist even longer.


Representative console output

Failed to load chunk /_next/static/chunks/<hash>._.js

from module [project]/components/heavy-module.ts [app-client]
(ecmascript, async loader)

  at async Promise.all
  at async Page.onClick

What I tried

1. Inline retry with .catch()

const mod = await import("../components/heavy-module").catch(async () => {
  await new Promise((r) => setTimeout(r, 300));
  return import("../components/heavy-module");
});

This still hits the same cached failure and rejects again.


2. Longer retry + waiting for online event

This improves things slightly but remains racy and fragile.

The failure cache TTL is not deterministic.


3. Full page reload

window.location.reload();

This reliably fixes the issue, but is disruptive because it destroys in-memory application state.


4. webpackPrefetch

This helps only for predictable navigation flows.

It does not help for interaction-triggered imports, and the prefetch itself can fail silently.


5. Replacing dynamic import with static import

This completely avoids the problem, but increases the initial bundle size.

At the moment, this is the most reliable workaround.


What I'd hope for

1. Built-in retry support in the Turbopack chunk loader

Ideally configurable via next.config.js, for example:

  • max retries
  • retry delay
  • exponential backoff strategy

Equivalent in spirit to webpack-retry-chunk-load-plugin.

There is related discussion in #82651 where retry support was mentioned as considered but not scheduled.


2. Stable runtime hooks

A documented runtime API/hook that lets applications implement retry logic globally rather than wrapping every import() call site manually.


3. Documentation clarification

Explicit documentation stating that:

  • dynamic import() failures are not automatically retried by Turbopack
  • failed chunk loads may remain cached temporarily by the runtime/browser
  • applications should implement retry handling if lazy-loaded modules are critical

Environment

  • Operating System: macOS / Linux / Windows

  • Node.js: <node -v>

  • Next.js: <package version>

  • Browser: Chrome <version>, Firefox <version>

  • Build command:

    next build --turbo
  • Dev command:

    next dev --turbo

Additional context

This is a real production concern for apps where important features are hidden behind dynamic imports:

  • editors
  • feature-flagged experiences
  • large SDKs
  • AI tooling
  • visualization libraries

Webpack users historically had community solutions like:

  • webpack-retry-chunk-load-plugin

Turbopack users currently do not have an equivalent option.

As a result, teams often end up:

  • reverting to webpack via next build --webpack
  • eagerly bundling everything
  • wrapping every dynamic import in custom retry logic
  • forcing full page reloads after chunk failures

Current vs. Expected behavior

Current behavior

When a dynamically imported chunk fails to load due to a transient network issue (offline state, flaky connection, temporary 5xx, etc.), subsequent import() calls for the same chunk continue to fail for a period of time even after connectivity is restored.

The failed state appears to be cached either by:

  • the Turbopack runtime
  • the browser's module loader / HTTP negative cache
  • or a service worker layer

As a result:

  • repeated retries immediately fail
  • short retry loops are ineffective
  • users often need to hard refresh the page
  • interaction-triggered lazy loading becomes unreliable on unstable networks

Expected behavior

Once connectivity is restored, a subsequent import() attempt should transparently retry fetching the chunk and succeed without requiring a page reload.

Ideally, Turbopack should provide one of the following:

  • automatic retry behavior for failed chunk loads
  • configurable retry/backoff support
  • or runtime hooks allowing global retry handling

At minimum, failed dynamic imports should not remain stuck in a cached rejected state for several seconds after the network is healthy again.

Provide environment information

Operating System:                                                                                                                                                                                                                                                                                     
    Platform: darwin                                                                                                                                                                                                                                                                                    
    Arch: arm64                                                                                                                                                                                                                                                                                         
    Version: Darwin 25.2.0                                                                                                                                                                                                                                                                              
    Available memory (MB): 24576                                                                                                                                                                                                                                                                        
    Available CPU cores: 10                                                                                                                                                                                                                                                                             
  Binaries:                                                                                                                                                                                                                                                                                             
    Node: 24.14.1                                                                                                                                                                                                                                                                                       
    npm: 11.12.1                                                                                                                                                                                                                                                                                        
    pnpm: 10.28.0
  Relevant Packages:                                                                                                                                                                                                                                                                                    
    next: 15.5.14
    react: 18.3.1                                                                                                                                                                                                                                                                                       
    react-dom: 18.3.1                                                                                                                                                                                                                                                                                 
    typescript: 5.8.3
  Next.js Config:                                                                                                                                                                                                                                                                                       
    output: N/A
  Build command: next build --turbo                                                                                                                                                                                                                                                                     
  Dev command: next dev --turbo

Which area(s) are affected? (Select all that apply)

Lazy Loading

Which stage(s) are affected? (Select all that apply)

Vercel (Deployed)

Additional context

No response

extent analysis

TL;DR

Implement a custom retry mechanism for dynamic imports in Turbopack, as there is no built-in retry support.

Guidance

  1. Wrap dynamic imports in a retry function: Create a higher-order function that catches import errors, waits for a short period, and retries the import.
  2. Use a library or implement exponential backoff: Consider using a library like retry-async or implement an exponential backoff strategy to handle repeated failures.
  3. Monitor network connectivity: Listen for the online event to detect when the network connection is restored and retry failed imports.
  4. Consider using a service worker: Implement a service worker to cache failed requests and retry them when the network connection is restored.

Example

const retryImport = async (importFunc, maxRetries = 3, retryDelay = 500) => {
  let retries = 0;
  while (retries < maxRetries) {
    try {
      return await importFunc();
    } catch (error) {
      retries++;
      if (retries < maxRetries) {
        await new Promise((resolve) => setTimeout(resolve, retryDelay));
      } else {
        throw error;
      }
    }
  }
};

const onClick = async () => {
  const importFunc = () => import("../components/heavy-module");
  const mod = await retryImport(importFunc);
  mod.run();
};

Notes

This solution assumes that the issue is caused by the lack of built-in retry support in Turbopack. The custom retry mechanism may need to be adjusted based on the specific requirements of the application.

Recommendation

Apply a custom retry workaround, as there is no built-in retry support in Turbopack, and the webpack-retry-chunk-load-plugin is not applicable.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

Once connectivity is restored, a subsequent import() attempt should transparently retry fetching the chunk and succeed without requiring a page reload.

Ideally, Turbopack should provide one of the following:

  • automatic retry behavior for failed chunk loads
  • configurable retry/backoff support
  • or runtime hooks allowing global retry handling

At minimum, failed dynamic imports should not remain stuck in a cached rejected state for several seconds after the network is healthy again.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING