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>
  );
}