Back to Blog
Performance8 min readApril 26, 2026

Code Splitting and Lazy Loading in React

A practical guide to ship less JavaScript up front by combining route-level and component-level lazy loading in React apps.

YA

Di Huynh

Author

Fast experiences start with smaller bundles. Code splitting and lazy loading are two core techniques in React that help you send only what users need, when they need it.

Route-based code splitting diagram
Route-level splitting keeps unrelated pages out of the initial bundle.

Why Bundle Size Becomes a Problem

As apps grow, the main JavaScript bundle often includes components that users may never open in a session. Large bundles slow down initial render, especially on mobile devices and weaker networks.

Route-Level Splitting First

The easiest win is route-level splitting. In frameworks like Next.js, this is mostly automatic, but custom React apps can implement it with React.lazy and route boundaries.

import { Suspense, lazy } from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";

const Login = lazy(() => import("./Login"));
const Dashboard = lazy(() => import("./Dashboard"));

export default function App() {
  return (
    <Router>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/" element={<Login />} />
          <Route path="/dashboard" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

Component-Level Lazy Loading

Component-based code splitting diagram
Component-level splitting is ideal for modal dialogs, editors, and charts.

For heavy components inside a page, lazy loading is still useful. Examples include chart libraries, rich text editors, map components, and advanced filters.

import { useState, lazy, Suspense } from "react";

const Modal = lazy(() => import("./Modal"));

export default function App() {
  const [showModal, setShowModal] = useState(false);

  return (
    <div>
      <button onClick={() => setShowModal(true)}>Open Modal</button>
      {showModal && (
        <Suspense fallback={<ModalSkeleton />}>
          <Modal onClose={() => setShowModal(false)} />
        </Suspense>
      )}
    </div>
  );
}

Prefetch for Likely Next Actions

Lazy loading is not only about delay. You can prefetch chunks when users hover a link or idle on a page.

// Webpack prefetch example
const Modal = lazy(() => import(/* webpackPrefetch: true */ "./Modal"));

Pro Tip: Optimization is complete only when metrics improve in production. Use Lighthouse to verify impact.

A Practical Checklist

  • Split by route first.
  • Lazy load heavy optional components (Charts, Editors).
  • Add polished fallbacks (Skeletons).
  • Prefetch likely-next chunks.
  • Measure LCP and TTI metrics.