Matthew C.
—When lazy loading components using React Suspense, you may encounter the following error:
Uncaught TypeError: Failed to fetch dynamically imported module: https://example.com/assets/Home-d165e21c.js
You may also get a ChunkLoad
error:
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:
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.
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:
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:
<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.
Tasty treats for web developers brought to you by Sentry. Get tips and tricks from Wes Bos and Scott Tolinski.
SEE EPISODESConsidered “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.
Here’s a quick look at how Sentry handles your personal information (PII).
×We collect PII about people browsing our website, users of the Sentry service, prospective customers, and people who otherwise interact with us.
What if my PII is included in data sent to Sentry by a Sentry customer (e.g., someone using Sentry to monitor their app)? In this case you have to contact the Sentry customer (e.g., the maker of the app). We do not control the data that is sent to us through the Sentry service for the purposes of application monitoring.
Am I included?We may disclose your PII to the following type of recipients:
You may have the following rights related to your PII:
If you have any questions or concerns about your privacy at Sentry, please email us at compliance@sentry.io.
If you are a California resident, see our Supplemental notice.