Sentry Answers>React>

Failed to fetch dynamically imported module in React

Failed to fetch dynamically imported module in React

Matthew C.

The ProblemThe Problem

When lazy loading components using React Suspense, you may encounter the following error:

Click to Copy
Uncaught TypeError: Failed to fetch dynamically imported module: https://example.com/assets/Home-d165e21c.js

You may also get a ChunkLoad error:

Click to Copy
Unhandled Runtime Error ChunkLoadError: Loading chunk _app-pages-browser_src_app_Home_tsx failed.

The React lazy function lazy-loads components only as they’re needed to improve performance.

For example, you may have a page that shows a complex map that relies on a large third-party library. You can use lazy loading to show the map and fetch its dependencies only if the user chooses to see the map by clicking a Show map button:

Click to Copy
import { Suspense, lazy, useState } from "react"; import Info from "./Info"; const Map = lazy(() => import("./Map")); export default function Directions() { const [showMap, setShowMap] = useState(false); return ( <div> <Info /> <button onClick={() => setShowMap(!showMap)}> {showMap ? "Hide map" : "Show map"} </button> {showMap ? ( <Suspense fallback={<div>Loading...</div>}> <Map /> </Suspense> ) : ( "" )} </div> ); }

The lazy function takes in a load argument and returns a Promise or another thenable. The load function must resolve to an object whose .default property is a valid React component type.

If the lazy-loaded component can’t be downloaded, you’ll get one of the above errors. This may occur due to a network issue such as a slow or intermittent internet connection.

Another common cause is a cache issue after deploying a new version of your app. React apps often use a bundler such as Vite or Webpack that adds a hash to the file names of assets. Assets include HTML, CSS, and JavaScript files. When a change is made to an asset, such as the JavaScript file for a module that is lazily imported, the file name hash changes. The hashes are added to change the file names each time the app is updated so that the browser does not use cached versions of the assets. This technique of changing an asset’s name, and subsequently its URL, is known as cache-busting.

However, if a user accesses your website before your app is updated, stays online during the update, and triggers lazy loading of the component file, the component import may fail. This may occur because the user has a browser-cached version of the page that has a URL for the old version of the React component file to be lazy-loaded that no longer exists.

The SolutionThe Solution

To handle network issues, you can create a wrapper function for the React lazy function that retries the import. The problem with just retrying the import is that the first import is cached, even if it’s a failed import. The retries would return the cached failed response. This module fetching behavior, where HTTP errors are cached, may change.

A basic solution to this issue is to catch chunk loading errors with a React error boundary component. A chunk error occurs if an application encounters issues loading a JavaScript chunk (bundle). You can use a React error boundary component to catch chunk loading errors and display a message to the user with a page refresh button:

Click to Copy
class ChunkErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { // Only show refresh for chunk load errors if ( error.name === "ChunkLoadError" || error.message.includes("Failed to fetch dynamically imported module") ) { return { hasError: true }; } return { hasError: false }; } render() { if (this.state.hasError) { return ( <div> <p>Failed to load component.</p> <button onClick={() => window.location.reload()} > Refresh page </button> </div> ); } return this.props.children; } }

Wrap your suspense boundary with the ChunkErrorBoundary component:

Click to Copy
<ChunkErrorBoundary> <Suspense fallback={<div>Loading...</div>}> <Map /> </Suspense> </ChunkErrorBoundary>

If the user clicks the Show map button and the Map component import fails, the user can click the Refresh page button to refresh the page. The next time the user clicks the Show map button, a fresh attempt to import the module will be made.

If you’re using Next.js with the App Router, you can easily create an error boundary by adding an error.tsx file inside a route segment and exporting an error component from it as explained in the Next.js docs.

Other possible, but more complex, solutions include creating versioned deployments and notifying users to refresh the application when a new version is detected. New versions can be detected by using a version number stored in an environment variable and added to URLs. Another way to detect a version change is to use a service worker.

Considered “not bad” by 4 million developers and more than 100,000 organizations worldwide, Sentry provides code-level observability to many of the world’s best-known companies like Disney, Peloton, Cloudflare, Eventbrite, Slack, Supercell, and Rockstar Games. Each month we process billions of exceptions from the most popular products on the internet.