Sentry Answers>React>

Failed to fetch dynamically imported module in React

Failed to fetch dynamically imported module in React

Matthew C.

The 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 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.

  • Sentry BlogGuide to Error & Exception Handling in React
  • Sentry BlogHow to identify fetch waterfalls in React
  • Syntax.fmReact Server Components
  • Sentry BlogSentry can’t fix React hydration errors, but it can really help you debug them
  • Syntax.fmWhy the jQuery Creator Uses React and Typescript
  • Syntax.fmListen to the Syntax Podcast
  • Sentry BlogReact Native Debugging and Error Tracking During App Development
  • Syntax.fmDiscussion on building native iOS and Android apps with React Native
  • SentryReact Error & Performance Monitoring
  • Sentry BlogFixing memoization-breaking re-renders in React
  • SentryReact Debug Hub
  • Syntax.fm logo
    Listen to the Syntax Podcast

    Tasty treats for web developers brought to you by Sentry. Get tips and tricks from Wes Bos and Scott Tolinski.

    SEE EPISODES

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.

© 2024 • Sentry is a registered Trademark of Functional Software, Inc.