Large JavaScript bundles can slow down your application. When too much code loads at once, users wait longer for the first paint and pages feel less responsive. Search engines may also rank slower sites lower in results.

Lazy loading helps solve this problem by splitting your code into smaller chunks and loading them only when they are needed

This guide walks you through lazy loading in React and Next.js. By the end, you’ll know when to use React.lazy, next/dynamic, and Suspense, and you’ll have working examples you can copy and adapt to your own projects.

Table of Contents

What is Lazy Loading?

Lazy loading is a performance technique that defers loading code until it’s needed. Instead of loading your entire app at once, you split it into smaller chunks. The browser only downloads a chunk when the user navigates to that route or interacts with that feature.

Benefits include:

  • Faster initial load: Smaller first bundle means quicker time to interactive

  • Better Core Web Vitals: Improves Largest Contentful Paint and Total Blocking Time

  • Lower bandwidth: Users only download what they use

In React, you achieve this with dynamic imports and React.lazy() or Next.js’s next/dynamic.

Prerequisites

Before you follow along, you should have:

  • Basic familiarity with React (components, hooks, state)

  • Node.js installed (version 18 or later recommended)

  • A React app (Create React App or Vite) or a Next.js app (for the Next.js examples)

For the React examples, you can use Create React App or Vite. For the Next.js examples, use the App Router (Next.js 13 or later).

How to Use React.lazy for Code Splitting

React.lazy() lets you define a component as a dynamic import. React will load that component only when it’s first rendered.

React.lazy() expects a function that returns a dynamic import(). The imported module must use a default export.

Here’s a basic example:

import { lazy } from 'react';

const HeavyChart = lazy(() => import('./HeavyChart'));
const AdminDashboard = lazy(() => import('./AdminDashboard'));

function App() {
  return (
    
  );
}

If you use named exports, you can map them to a default export:

const ComponentWithNamedExport = lazy(() =>
  import('./MyComponent').then((module) => ({
    default: module.NamedComponent,
  }))
);

You can also name chunks for easier debugging in the browser:

const HeavyChart = lazy(() =>
  import(/* webpackChunkName: "heavy-chart" */ './HeavyChart')
);

React.lazy() alone isn’t enough. You must wrap lazy components in Suspense so React knows what to show while they load.

How to Use Suspense with React.lazy

Suspense is a React component that shows a fallback UI while its children are loading. It works with React.lazy() to handle the loading state of dynamically imported components.

Wrap your lazy components in Suspense and provide a fallback prop:

import { lazy, Suspense } from 'react';

const HeavyChart = lazy(() => import('./HeavyChart'));
const AdminDashboard = lazy(() => import('./AdminDashboard'));

function App() {
  return (
    
      Loading chart...}>
        
      
      Loading dashboard...