Suggest an editImprove this articleRefine the answer for “React.lazy And suspense — lazy components in React”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**React.lazy** loads a component dynamically, keeping it out of the initial bundle until it renders. Wrap it in **Suspense** to show a fallback during loading. ```jsx const Chart = lazy(() => import('./Chart')); function App() { return ( <Suspense fallback={<p>Loading...</p>}> <Chart /> </Suspense> ); } ``` **Key point:** lazy + Suspense = route-level code splitting with no extra setup.Shown above the full answer for quick recall.Answer (EN)Image**React.lazy** is a function that loads a React component dynamically, skipping it in the initial bundle until it actually renders. Pair it with **Suspense** to show a fallback during that load, and you get code splitting with almost no configuration. ## Theory ### TL;DR - `React.lazy()` wraps a dynamic `import()` and returns a component React can render normally - `Suspense` catches the loading state and shows a fallback UI (spinner, skeleton, placeholder) - Webpack and Vite automatically create separate chunk files for lazy-loaded modules - Only works with **default exports** - named exports need one extra step - Best for route-level splits, heavy modals, and anything users rarely see on first load ### Quick example ```jsx import React, { lazy, Suspense } from 'react'; // Vite/Webpack splits this into a separate chunk automatically const Dashboard = lazy(() => import('./pages/Dashboard')); function App() { return ( <Suspense fallback={<p>Loading...</p>}> <Dashboard /> </Suspense> ); } ``` When `Dashboard` renders for the first time, React pauses, fetches the chunk, then finishes rendering. The `fallback` prop is what users see in the meantime. ### How the split happens The bundler sees `import('./pages/Dashboard')` and creates a separate `.js` file for that module. `React.lazy` wraps the result in a Promise. When the component renders for the first time, React throws that Promise internally, Suspense catches it, renders the fallback, and re-renders once the Promise resolves. You don't touch any of that machinery. The API is `lazy + Suspense`, the rest is automatic. ### When to use - **Route-level splitting**: every page behind a route is a good candidate. Users on `/home` don't need the `/settings` bundle downloading immediately. - **Heavy third-party UI**: rich text editors, chart libraries, PDF viewers - lazy-load them. - **Modals and drawers**: components that open on a button click load only when the user actually clicks. - **Admin sections**: parts of the app most users never open. Don't lazy-load tiny components or anything that appears immediately on every page render. The async overhead is real, even if small. ### Named exports and lazy `React.lazy` expects the dynamic import to resolve to a module with a **default export**. If your component uses a named export, wrap it like this: ```jsx // UserCard uses a named export const UserCard = lazy(() => import('./UserCard').then((mod) => ({ default: mod.UserCard })) ); ``` This is not a bug - it's just how the API is designed. I've seen this trip up teams who assumed lazy works with anything importable. ### Common mistakes **Forgetting Suspense entirely:** ```jsx // This throws at runtime const Chart = lazy(() => import('./Chart')); function Dashboard() { return <Chart />; // No Suspense above - React will error } ``` Lazy components need a Suspense boundary somewhere above them in the tree. One boundary can cover multiple lazy components at once. **Defining lazy inside a component body:** ```jsx // Bad: re-creates the lazy reference on every render function Page() { const Chart = lazy(() => import('./Chart')); // wrong return <Chart />; } ``` Always define lazy components at the module level, outside any function. **No error boundary in production:** Network requests fail. If the chunk download errors, React crashes the whole tree unless you have an `ErrorBoundary` around the `Suspense`. In production, always pair them: ```jsx <ErrorBoundary fallback={<p>Failed to load.</p>}> <Suspense fallback={<Spinner />}> <LazyPage /> </Suspense> </ErrorBoundary> ``` ### Real-world usage - React Router apps: lazy-load every route component, one `Suspense` at the router level - Next.js: uses `next/dynamic`, which wraps the same pattern with extra options like `ssr: false` - Component libraries: ship heavy components (DataGrid, RichEditor) as separate entry points so consumers can lazy-load them ### Follow-up questions **Q:** Can one Suspense boundary wrap multiple lazy components? **A:** Yes. Suspense shows the fallback if any lazy child is still loading. Once all resolve, everything renders. This is useful for route-level splits with several lazy parts on one page. **Q:** What happens when a lazy component's chunk fails to load? **A:** React throws an error that propagates up the tree. Without an ErrorBoundary, the whole app unmounts. With one, only the boundary's subtree shows the error fallback. **Q:** Does React.lazy work with Server Components in React 18+? **A:** Not directly. On the server, dynamic imports work differently. Next.js handles this with `next/dynamic` and a `{ ssr: false }` option. Pure `React.lazy` is client-only. **Q:** What is the difference between React.lazy and next/dynamic? **A:** `next/dynamic` wraps `React.lazy` and adds options: `ssr: false` to skip server rendering, and a built-in `loading` prop instead of requiring a Suspense wrapper. Same concept, different API layer. ## Examples ### Basic lazy component ```jsx import React, { lazy, Suspense } from 'react'; const HeavyChart = lazy(() => import('./HeavyChart')); function ReportPage() { return ( <div> <h1>Monthly Report</h1> <Suspense fallback={<div>Loading chart...</div>}> <HeavyChart data={reportData} /> </Suspense> </div> ); } ``` `HeavyChart` and its dependencies ship in a separate file. Users who never visit `ReportPage` never download that code. ### Route-level code splitting with React Router ```jsx import { BrowserRouter, Routes, Route } from 'react-router-dom'; import React, { lazy, Suspense } from 'react'; const Home = lazy(() => import('./pages/Home')); const Settings = lazy(() => import('./pages/Settings')); const AdminPanel = lazy(() => import('./pages/AdminPanel')); function App() { return ( <BrowserRouter> <Suspense fallback={<div>Loading page...</div>}> <Routes> <Route path="/" element={<Home />} /> <Route path="/settings" element={<Settings />} /> <Route path="/admin" element={<AdminPanel />} /> </Routes> </Suspense> </BrowserRouter> ); } ``` One `Suspense` at the router level covers all routes. Each page loads only when the user navigates to it. The admin bundle never reaches a regular user unless they visit `/admin`.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.