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.
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.
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.
{
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
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.
Frequently Asked Questions
Does lazy loading help every app?
What should the Suspense fallback be?
Can I lazy load a loader too?
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.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.