How to Update Lazy Loading in React
This article provides a direct guide on how to update and optimize
lazy loading in React applications. You will learn how to implement
standard lazy loading using React.lazy and
Suspense, update your codebase to handle dynamic
route-based imports, resolve common chunk loading errors during
deployments, and leverage React 18 features to improve the user
experience during loading state transitions.
Implementing the Standard Lazy Loading Pattern
To update a standard component import to a lazy-loaded one, you must
replace static import statements with the React.lazy()
function and wrap the component inside a Suspense
boundary.
First, locate your static import:
import HeavyComponent from './HeavyComponent';Update it to a dynamic import using React.lazy:
import React, { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading component...</div>}>
<HeavyComponent />
</Suspense>
</div>
);
}The Suspense component requires a fallback
prop, which renders placeholder UI (like a spinner or skeleton screen)
while the lazy component loads.
Updating Route-Based Lazy Loading
One of the most effective places to update lazy loading is within your application routes. When updating to modern routing libraries like React Router v6, you can lazy load entire page layouts to split your bundle size.
Here is how to update your routing structure:
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import React, { lazy, Suspense } from 'react';
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function AppRoutes() {
return (
<Router>
<Suspense fallback={<div>Loading Page...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</Router>
);
}Handling Chunk Loading Failures on Deployments
A common issue when updating React apps is the
ChunkLoadError. This happens when you deploy a new version
of your application, changing the build hashes, which causes active
users to request outdated, non-existent lazy-loaded files.
To fix this, update your dynamic imports with a recovery mechanism that automatically reloads the page to fetch the latest build:
const lazyWithRetry = (componentImport) => {
return lazy(() =>
componentImport().catch((error) => {
const hasRetried = window.localStorage.getItem('lazy-retry');
if (!hasRetried) {
window.localStorage.setItem('lazy-retry', 'true');
window.location.reload();
} else {
window.localStorage.removeItem('lazy-retry');
throw error;
}
})
);
};
// Use the helper to update your lazy components
const AdminPanel = lazyWithRetry(() => import('./AdminPanel'));Updating to React 18 Transitions
If you are using React 18 or newer, you can update your lazy loading
UX by preventing unwanted loading fallbacks during state changes. By
using the useTransition hook, React will keep the old UI
visible while the new lazy-loaded component resolves in the
background.
import React, { useState, useTransition, lazy, Suspense } from 'react';
const ProfileTab = lazy(() => import('./ProfileTab'));
const SettingsTab = lazy(() => import('./SettingsTab'));
function TabContainer() {
const [tab, setTab] = useState('profile');
const [isPending, startTransition] = useTransition();
const handleTabChange = (nextTab) => {
startTransition(() => {
setTab(nextTab);
});
};
return (
<div>
<button onClick={() => handleTabChange('profile')}>Profile</button>
<button onClick={() => handleTabChange('settings')}>Settings</button>
{isPending && <span> Updating UI...</span>}
<Suspense fallback={<div>Loading new tab...</div>}>
{tab === 'profile' && <ProfileTab />}
{tab === 'settings' && <SettingsTab />}
</Suspense>
</div>
);
}