🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
REFERENCEreact

react Documentation

LOADING ENGINE...

Performance Optimization with Lazy Loading

AI & DATA SCIENCE // lazy-loading

React.lazy() lets a component's code be loaded on demand, split into a separate JavaScript bundle chunk that's only downloaded when that component is actually needed, rather than upfront.

Syntax

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

Deep Dive Course

React.lazy() takes a function that calls the standard dynamic import() syntax, returning a promise for the component's module, and produces a component that can be rendered normally but triggers its underlying code chunk to actually download the first time it's rendered, rather than being included in the application's initial bundle. Because that download is asynchronous, a lazily-loaded component must be wrapped in a Suspense boundary somewhere above it, which displays a fallback UI while the chunk is still loading — this pattern, called code-splitting, is especially valuable for large components or entire routes that aren't needed on the initial page load, meaningfully shrinking the amount of JavaScript a user has to download upfront.

1Understanding Performance Optimization with Lazy Loading

React.lazy() takes a function that calls the standard dynamic import() syntax, returning a promise for the component's module, and produces a component that can be rendered normally but triggers its underlying code chunk to actually download the first time it's rendered, rather than being included in the application's initial bundle. Because that download is asynchronous, a lazily-loaded component must be wrapped in a Suspense boundary somewhere above it, which displays a fallback UI while the chunk is still loading — this pattern, called code-splitting, is especially valuable for large components or entire routes that aren't needed on the initial page load, meaningfully shrinking the amount of JavaScript a user has to download upfront.

💡

Code-split entire routes with React.lazy() rather than individual small components — splitting a whole page/route that isn't needed until the user actually navigates there typically gives a much more meaningful reduction in initial bundle size than lazy-loading small, already-cheap components.

editor.html
import React, { Suspense } from 'react';

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

function App() {
  return (
    <Suspense fallback={<p>Loading settings...</p>}>
      <Settings />
    </Suspense>
  );
}
localhost:3000

2Practical Example

Here is a real-world application of Performance Optimization with Lazy Loading showing how it is used in production React code.

editor.html
// Without lazy loading, Settings' code is part of the main bundle from the start
// import Settings from './Settings';

// With lazy loading, it's a separate chunk downloaded only when rendered
const Settings = React.lazy(() => import('./Settings'));
localhost:3000

3Best Practices

Follow these guidelines when working with Performance Optimization with Lazy Loading:

1. Apply React.lazy() to entire routes or large, infrequently-needed components, like a rarely-opened modal or admin panel, rather than small components where the splitting overhead outweighs the benefit

2. Always wrap a lazily-loaded component in a Suspense boundary with an appropriate fallback, since React throws an error if a lazy component renders without one somewhere above it

3. Combine React.lazy() with your router's own route-based code-splitting conventions when using a framework like React Router or Next.js, rather than reinventing route-level splitting manually

⚠️

Tip: Code-split entire routes with React.lazy() rather than individual small components — splitting a whole page/route that isn't needed until the user actually navigates there typically gives a much more meaningful reduction in initial bundle size than lazy-loading small, already-cheap components.

editor.html
import React, { Suspense } from 'react';

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

function App() {
  return (
    <Suspense fallback={<p>Loading settings...</p>}>
      <Settings />
    </Suspense>
  );
}
localhost:3000

Examples

Example 01Basic Usage
import React, { Suspense } from 'react';

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

function App() {
  return (
    <Suspense fallback={<p>Loading settings...</p>}>
      <Settings />
    </Suspense>
  );
}
Example 02Advanced Example
// Without lazy loading, Settings' code is part of the main bundle from the start
// import Settings from './Settings';

// With lazy loading, it's a separate chunk downloaded only when rendered
const Settings = React.lazy(() => import('./Settings'));

Best Practices

  • Apply React.lazy() to entire routes or large, infrequently-needed components, like a rarely-opened modal or admin panel, rather than small components where the splitting overhead outweighs the benefit
  • Always wrap a lazily-loaded component in a Suspense boundary with an appropriate fallback, since React throws an error if a lazy component renders without one somewhere above it
  • Combine React.lazy() with your router's own route-based code-splitting conventions when using a framework like React Router or Next.js, rather than reinventing route-level splitting manually

Interview Question

Why must a component created with React.lazy() always be rendered inside a Suspense boundary, unlike a normally-imported component?

Hint: Think about what happens the very first time a lazy component renders, before its actual code has finished downloading.

A normally-imported component's code is already fully present in the loaded JavaScript bundle by the time the component tries to render it, so there's no waiting period at all, rendering can proceed immediately and synchronously. A React.lazy()-created component, by contrast, triggers an asynchronous dynamic import() the first time it's rendered, and that network request/module download takes real time to complete, during which there's genuinely no component code yet available to actually render anything with. React handles this gap by having the lazy component suspend rendering, effectively pausing that part of the tree, and looking upward for the nearest enclosing Suspense boundary to display its fallback content instead while the download is still in progress — without a Suspense boundary present somewhere above it, React has no fallback content to show during that necessarily asynchronous waiting period, which is exactly why it throws an error rather than leaving the screen in some undefined, incomplete state.

Exercises

MediumPractice using Performance Optimization with Lazy Loading in a real scenario.
View Solution
import React, { Suspense } from 'react';

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

function App() {
  return (
    <Suspense fallback={<p>Loading settings...</p>}>
      <Settings />
    </Suspense>
  );
}

Frequently Asked Questions

Why must a component created with React.lazy() always be rendered inside a Suspense boundary, unlike a normally-imported component?

A normally-imported component's code is already fully present in the loaded JavaScript bundle by the time the component tries to render it, so there's no waiting period at all, rendering can proceed immediately and synchronously. A React.lazy()-created component, by contrast, triggers an asynchronous dynamic import() the first time it's rendered, and that network request/module download takes real time to complete, during which there's genuinely no component code yet available to actually render anything with. React handles this gap by having the lazy component suspend rendering, effectively pausing that part of the tree, and looking upward for the nearest enclosing Suspense boundary to display its fallback content instead while the download is still in progress — without a Suspense boundary present somewhere above it, React has no fallback content to show during that necessarily asynchronous waiting period, which is exactly why it throws an error rather than leaving the screen in some undefined, incomplete state.

Related Functions

suspenseconditional-component-renderingerror-boundaries