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-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.

JSX
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-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.

JSX
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.

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

Tip

Optimization is complete only when metrics improve in production. Use Lighthouse and real-user data to verify the 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.

When applied with intent, code splitting and lazy loading can make React apps feel significantly faster without major rewrites.