React.lazy

Code-Split a Component to Shrink the Initial Bundle

React.lazy

`React.lazy` lets a component load on demand. Pair with `<Suspense>` for the loading UI.

3 min read Level 2/5 #react#lazy#code-splitting
What you'll learn
  • Lazy-load a component
  • Combine with `<Suspense>`
  • Pick what to split

By default, your whole React app ships in one JavaScript bundle. Big bundle → slow first paint. React.lazy lets you split off parts that aren’t needed up-front.

The API

import { lazy, Suspense } from "react";

const Settings = lazy(() => import("./Settings.jsx"));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Settings />
    </Suspense>
  );
}

The import("...") is a dynamic import — the bundler (Vite, webpack) turns it into a separate chunk that downloads only when the lazy component first renders.

What To Split

Good candidates:

  • Routes — settings page, admin page, infrequently visited pages
  • Modals — the modal’s code only loads when the user opens it
  • Heavy third-party widgets — chart libraries, rich text editors
  • Anything below the fold or behind a click

Bad candidates:

  • Tiny components — the request overhead is larger than the payload
  • Stuff that’s always visible on first load

Per-Route Split

Common with React Router:

const Home = lazy(() => import("./Home.jsx"));
const Settings = lazy(() => import("./Settings.jsx"));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

Visiting /settings for the first time triggers a chunk download for that route only. Everything else loads on demand.

Default vs Named Exports

React.lazy only supports default exports out of the box. For named exports, re-wrap:

// somewhere
const Settings = lazy(() => import("./Settings.jsx").then(m => ({ default: m.Settings })));

Up Next

Forwarding refs through your component.

forwardRef →