Skip to content

React Performance: Code Splitting with Lazy & Suspense

Posted on:January 19, 2026 at 10:00 AM

Hello, Code Artisans! đź‘‹

I see you’ve been building some awesome React applications. Components left and right, features piling up. But then you notice something…

”Why does my app take 5 seconds to show up on my phone?” 🤔

The answer often lies in your Bundle Size. By default, React (and tools like Webpack or Vite) bundles everything into one giant JavaScript file. When a user visits your homepage, they are downloading the code for the Settings page, the Dashboard, and the Profile page—even if they haven’t logged in yet!

Today, we’re fixing that. We’re talking about Code Splitting with React.lazy and Suspense.

What is Code Splitting?

Imagine you’re packing for a trip.

In React terms, instead of sending one 5MB file, we send a small 200KB file for the homepage. The rest is downloaded only when the user needs it.

The Dynamic Duo: Lazy & Suspense

React 18 makes this super easy. We don’t need complex configurations anymore.

1. The Old Way (Static Import)

Usually, you import components at the top of your file:

import React from 'react';
import HeavyWidget from './HeavyWidget'; // <--- Loaded immediately!

const App = () => {
  return (
    <div>
      <h1>My App</h1>
      <HeavyWidget />
    </div>
  );
};

This means HeavyWidget is included in the main bundle, even if it’s hidden or at the bottom of the page.

2. The New Way (Lazy Load)

Let’s use React.lazy to import it dynamically.

import React, { lazy, Suspense } from 'react';

// This component is NOT loaded until we try to render it
const HeavyWidget = lazy(() => import('./HeavyWidget'));

const App = () => {
  return (
    <div>
      <h1>My App</h1>

      {/* We need Suspense to show something while the code is downloading */}
      <Suspense fallback={<div>Loading widget... ⏳</div>}>
        <HeavyWidget />
      </Suspense>
    </div>
  );
};

What just happened?

  1. We replaced import ... from ... with lazy(() => import(...)).
  2. We wrapped the component in <Suspense>.
  3. We provided a fallback UI (like a spinner or text).

Now, the code for HeavyWidget is split into a separate file (chunk). It is only downloaded from the server when <HeavyWidget /> is actually rendered.

When Should I Use This?

Don’t lazy load everything. Splitting too much can actually hurt performance (too many network requests).

Good Candidates for Lazy Loading:

  1. Routes: If you use React Router, definitely lazy load your pages (/dashboard, /settings, etc).
  2. Heavy Components: Big charts, maps, or rich text editors.
  3. Modals/Dialogs: Things that aren’t visible when the page first loads.

Real World Example: Route-based Splitting

This is the most common use case.

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

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

const App = () => (
  <Router>
    {/* One Suspense to rule them all! */}
    <Suspense fallback={<div>Loading page...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  </Router>
);

Conclusion

Code splitting is one of the most impactful performance optimizations you can do. It keeps your initial load fast and your users happy.

Start by splitting your Routes. Then, hunt down those heavy components.

Keep it light, keep it fast! 🚀