Code Splitting and Lazy Loading in React

Table of Contents

  1. Introduction
  2. What is Code Splitting?
  3. Why Code Splitting Matters
  4. React.lazy and Suspense
  5. Basic Example of Lazy Loading a Component
  6. Suspense Fallback UI
  7. Lazy Loading Routes with React Router
  8. Dynamic Imports for Non-Component Code
  9. Code Splitting with Webpack and Vite
  10. Best Practices and Caveats
  11. Conclusion

1. Introduction

React applications grow quickly, and without careful planning, you can end up delivering a huge JavaScript bundle to every user—regardless of whether they need all of it. This increases load times and hurts user experience, especially on slower connections.

Code splitting is a powerful technique that allows you to divide your app into smaller chunks, loading them only when needed. Combined with lazy loading, it significantly boosts your application’s performance and responsiveness.


2. What is Code Splitting?

Code splitting is the practice of breaking up your JavaScript bundle into multiple smaller pieces. These chunks are loaded on demand, instead of being loaded all at once during the initial page load.

Most modern bundlers like Webpack and Vite support code splitting automatically when using dynamic imports.


3. Why Code Splitting Matters

  • Faster Initial Load: Only necessary code is loaded.
  • Improved Time-to-Interactive (TTI): Less JavaScript to parse and execute.
  • Efficient Resource Usage: Saves bandwidth and memory, especially on mobile.
  • Scalability: Makes large apps manageable and performant.

4. React.lazy and Suspense

React introduced React.lazy and Suspense to enable component-level code splitting with an easy-to-use API.

React.lazy

This function lets you define a component that is loaded dynamically using import():

jsxCopyEditconst MyComponent = React.lazy(() => import('./MyComponent'));

5. Basic Example of Lazy Loading a Component

jsxCopyEditimport React, { Suspense } from 'react';

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

function App() {
  return (
    <div>
      <h1>Welcome</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <About />
      </Suspense>
    </div>
  );
}

Here, the About component is loaded only when needed, and a fallback UI is shown until it finishes loading.


6. Suspense Fallback UI

Suspense requires a fallback prop, which is rendered while the component is being loaded.

Common examples:

  • Spinners
  • Skeleton screens
  • Placeholder messages
jsxCopyEdit<Suspense fallback={<div>Loading content...</div>}>
  <LazyComponent />
</Suspense>

Note: Currently, Suspense only works for code-split components, not for data fetching unless you’re using React Server Components or frameworks like Next.js.


7. Lazy Loading Routes with React Router

In larger apps, routes are ideal for lazy loading. Here’s how you can implement it using React Router v6:

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

const Home = lazy(() => import('./pages/Home'));
const Profile = lazy(() => import('./pages/Profile'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading page...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/profile" element={<Profile />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

Each route component is loaded only when navigated to, reducing the initial bundle size.


8. Dynamic Imports for Non-Component Code

You can also dynamically import utility functions, JSON data, or configuration files:

jsCopyEditasync function loadHelpers() {
  const helpers = await import('./utils/helpers');
  helpers.doSomething();
}

This enables even finer control over what code is bundled and when it is executed.


9. Code Splitting with Webpack and Vite

Both Webpack and Vite support code splitting out of the box when using import():

  • Webpack creates separate chunks automatically and names them based on file paths.
  • Vite uses ES modules and supports code splitting with blazing speed.

With Webpack, you can name chunks like this:

jsCopyEditReact.lazy(() => import(/* webpackChunkName: "about" */ './About'));

10. Best Practices and Caveats

  • Keep your fallback UIs minimal and fast.
  • Group related components into feature chunks instead of many small ones.
  • Avoid overusing lazy loading—it can lead to waterfall loading patterns.
  • Test lazy-loaded components thoroughly, especially under slow network conditions.
  • Use tools like Lighthouse, Webpack Bundle Analyzer, or Vite Visualizer to inspect bundle sizes.

11. Conclusion

Code splitting and lazy loading are vital performance strategies in modern React development. They help reduce load times, boost user experience, and make your application more scalable.

With React.lazy and Suspense, implementing these techniques is easier than ever. For routing and larger feature sets, use dynamic imports with route-based lazy loading.