Lazy Loading

Master React lazy loading — the technique for reducing initial bundle size by splitting code into chunks that load on demand. Learn React.lazy, Suspense, and route-based code splitting.

Last reviewed: January 2025

·React 16.6+React 18React 19
✅
Quick Understanding: React.lazy lets you import a component only when it is first rendered. Combined with Suspense (which shows a fallback while loading), it splits your app into smaller chunks that load on demand. The result: a much smaller initial bundle and faster first load.

🔍 What Is It?

React.lazy(() => import('./Component')) creates a lazy component that is only loaded when React first tries to render it.

<Suspense fallback={<Spinner />}> wraps lazy components and shows the fallback UI while the chunk is loading.

const AdminPanel = React.lazy(() => import('./pages/AdminPanel'));

function App() {
  return (
    <Suspense fallback={<PageSpinner />}>
      <AdminPanel />
    </Suspense>
  );
}

The AdminPanel code is not included in the initial bundle. It downloads as a separate chunk only when a user navigates to a route that renders it.

🎯 Why Do We Need It?

A typical React app without code splitting ships one large JavaScript bundle. Every page's code is included even if the user never visits that page. For an app with 30 routes:

  • →Without lazy loading: user downloads code for all 30 pages on first load
  • →With lazy loading: user downloads only the code for the current page; other pages load on demand

This directly improves:

  • →Time to Interactive (TTI) — the app becomes usable faster
  • →Core Web Vitals — Google ranking signals
  • →User experience — especially on slow mobile connections

🏗️ What Happens in Real Projects?

In a large enterprise app:

  • →Lazy load all page-level route components (most impactful)
  • →Lazy load heavy third-party components: rich text editors, chart libraries, PDF viewers
  • →Lazy load admin sections that most users never access
  • →Lazy load modal content that is rarely opened

⚙️ How It Works

Bundlers (Vite, Webpack) recognise dynamic import() calls as split points. When you write React.lazy(() => import('./AdminPanel')), the bundler:

  1. Creates a separate chunk file for AdminPanel and its dependencies
  2. Generates a manifest mapping the chunk to the component
  3. At runtime, when React tries to render the lazy component, it loads the chunk via a script tag
  4. Suspense shows the fallback until the chunk is loaded and the component is ready

💻 Example

Route-Based Code Splitting — Lazy loading all page components with Suspense boundaries.

tsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

// Each import() creates a separate bundle chunk
const DashboardPage  = lazy(() => import('./pages/DashboardPage'));
const ClaimsPage     = lazy(() => import('./pages/ClaimsPage'));
const ClaimDetail    = lazy(() => import('./pages/ClaimDetailPage'));
const ReportsPage    = lazy(() => import('./pages/ReportsPage'));
const AdminPanel     = lazy(() => import('./pages/AdminPanel'));   // large chunk
const RichTextEditor = lazy(() => import('./components/Editor'));  // heavy library

// Named chunks for better debugging
const AnalyticsDashboard = lazy(
  () => import(/* webpackChunkName: "analytics" */ './pages/AnalyticsDashboard')
);

export function AppRoutes() {
  return (
    // Single Suspense at route level
    <Suspense fallback={<PageSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<DashboardPage />} />
        <Route path="/claims" element={<ClaimsPage />} />
        <Route path="/claims/:id" element={<ClaimDetail />} />
        <Route path="/reports" element={<ReportsPage />} />
        <Route path="/admin" element={<AdminPanel />} />
      </Routes>
    </Suspense>
  );
}

🤔 Common Confusions

❓ Can I use React.lazy for non-route components?

Yes. Any component can be lazy loaded. The most impactful use is route-level splitting, but you can also lazy load heavy components like rich text editors, data grids, or chart libraries that are not always visible.

❓ What happens if a chunk fails to load?

React throws an error that propagates to the nearest Error Boundary. Without an Error Boundary, the app crashes. Always wrap lazy components in both Suspense and an Error Boundary in production. Chunk loading failures happen on network errors or after a deployment when old chunk hashes are cached.

🚨 Common Production Issues

**Chunk loading failure after deployment** — When you deploy a new version, old chunk filenames change. Users with the old app cached try to load chunks that no longer exist. Fix: wrap lazy routes in an Error Boundary that shows a "Please refresh" message.

**Waterfall loading** — Nested lazy components load sequentially (parent loads, then child loads). Fix: hoist Suspense boundaries and preload critical chunks.

**Missing Error Boundary** — A network error during chunk loading crashes the app silently. Always wrap lazy components with an Error Boundary in production.

✅ Best Practices

Lazy load all page-level route components — highest impact

Always wrap lazy components in both Suspense and an Error Boundary

Use meaningful chunk names for debugging: webpackChunkName comments

Preload chunks on hover for instant navigation: import() in onMouseEnter

Measure bundle size with your bundler's analyse tool before and after

🏛️ Architect's Note

Senior Architect's Perspective

Code splitting is one of the highest-impact performance optimisations you can apply to a React app. The rule: lazy load everything at the route level by default. Do not wait until the app is slow — build with lazy loading from the start. In a large enterprise app, route-level code splitting can reduce the initial bundle by 60–80%, directly improving Time to Interactive on slow connections.

💬 Frequently Asked Questions

Does React.lazy work with named exports?

React.lazy requires a default export. For named exports, re-export as default in the import: lazy(() => import("./Component").then(m => ({ default: m.NamedComponent }))). Alternatively, add a default export to the component file.

How do I preload a lazy component before the user navigates to it?

Call the import() function directly (without React.lazy) to start loading the chunk: const preload = () => import("./HeavyPage"). Trigger this on hover or on a predictable user action. The chunk is cached by the browser, so when React.lazy tries to load it, it is already available.

🎯 Key Takeaways

1React.lazy + Suspense splits the bundle and loads code on demand
2Route-level lazy loading has the highest impact on initial load performance
3Always wrap lazy components with both Suspense and Error Boundary
4Chunk loading can fail — provide a retry mechanism in the Error Boundary
5Named chunk comments help identify chunks in bundle analysis

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these React references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide React learning path

Browse the full React topic collection and learning order on SplashRide.

React Interview Prep

Practice React interview questions and scenario-based answers.

Frontend Engineer Career Path

A beginner-friendly journey through web foundations, React, production frontend engineering, and interview preparation.

Python Developer Career Path

A practical route through Python fundamentals, automation, backend basics, testing, projects, interview preparation, and career growth.

React.memo

Master React.memo — the higher-order component for preventing unnecessary re-renders. Learn when it helps, when it hurts, and how to use it correctly with useCallback and useMemo.

Code Splitting & Performance

Master React performance optimisation — from bundle splitting to render optimisation. Learn the strategies senior engineers use to keep large React applications fast.