How to Lazy Load Routes in React

Lazy load routes in React with lazy and Suspense. Split route components into smaller bundles and show a fallback while they load.

5 min read

You can lazy load routes in React to split a large app into smaller bundles that download only when a route is visited. The lazy function and Suspense do the splitting in declarative mode, while data mode has a route-level lazy option.

Splitting defers the cost of heavy pages until the user actually opens them, which can speed up the first load.

Lazy load a route component

Wrap a dynamic import in the lazy function to turn a route component into a deferred import that loads on demand.

App.jsxApp.jsx
import { lazy } from "react";
 
const Dashboard = lazy(() => import("./Dashboard.jsx"));
const Settings = lazy(() => import("./Settings.jsx"));

These modules are not bundled into the initial page load. They download the first time their route matches.

Suspense then shows a fallback until a lazy component finishes loading.

App.jsxApp.jsx
import { Suspense } from "react";
import { Routes, Route } from "react-router";
export default function App() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

When /dashboard opens, the Dashboard bundle is fetched and the fallback shows in its place. Once loaded, the real page replaces the fallback.

Suspense must wrap the lazy components somewhere above the Routes, not inside each page.

Choose a small fallback

The fallback should be a lightweight spinner or a skeleton of the page area. Keep it small so it appears instantly, and avoid a full-page layout that causes a visible jump when the route arrives.

A skeleton shaped like the coming page reduces the jump more than a centered spinner, because it reserves the right amount of space.

Reserve the space the page will occupy, then fill it when the bundle arrives.

Lazy load in data mode

Data mode supports lazy on the route object itself, so the component and its loader split together.

App.jsxApp.jsx
{
  path: "/dashboard",
  lazy: async () => {
    const { Component, loader } = await import("./Dashboard.jsx");
    return { Component, loader };
  },
}

The router waits for lazy to resolve before rendering the route, and it loads the route's loader from the same chunk. Data mode and route loaders are covered in React Router loaders and actions explained. For a Vite project, the same dynamic import works out of the box because the bundler splits each import into its own file, as shown in how to add React Router to a Vite project.

The router handles the pending state automatically, so no Suspense wrapper is needed in data mode.

Split only when it helps

A route is worth splitting when its component and its imports are large enough to delay the first paint. Small pages already load fast, and each extra chunk adds a request. Check the bundle size in your build output before and after splitting to confirm the win.

Common mistakes

  • Splitting tiny pages that would load faster together.
  • Forgetting Suspense, which leaves lazy components with no fallback.
  • Using a heavy fallback that shifts the layout when content arrives.
Rune AI

Rune AI

Key Insights

  • Wrap a dynamic import in lazy to defer a route component.
  • Render lazy components inside Suspense with a small fallback.
  • Use the route-level lazy option in data mode.
  • Skip lazy loading for routes that are already small.
RunePowered by Rune AI

Frequently Asked Questions

Does lazy loading help every app?

Only apps with large route components. Small pages already load fast, so splitting them can add requests without a real benefit.

What should the Suspense fallback be?

A lightweight spinner or skeleton for the page area. Keep it small so it appears quickly and does not shift the layout.

Can I lazy load a loader too?

Yes. In data mode, the route-level lazy option splits the component and its loader into the same chunk.

Conclusion

Lazy loading routes splits a React app into bundles that load only when a route is visited. Wrap dynamic imports in lazy, render routes inside Suspense, and use the route-level lazy option in data mode.