DEV Community

Cover image for React Mastery Series – Day 25: React Performance Optimization – Lazy Loading, Code Splitting & Suspense
Siva Samanthapudi
Siva Samanthapudi

Posted on

React Mastery Series – Day 25: React Performance Optimization – Lazy Loading, Code Splitting & Suspense

Welcome back to the React Mastery Series!

In the previous article, we explored React Forms and learned how to build scalable forms using:

  • Controlled Components
  • Uncontrolled Components
  • React Hook Form
  • Validation
  • Zod
  • Enterprise form architecture

Today, we'll explore one of the most important topics for production applications:

React Performance Optimization

As applications grow, they also become larger.

A small React application might load:

250 KB
Enter fullscreen mode Exit fullscreen mode

An enterprise application can easily exceed:

5 MB+
Enter fullscreen mode Exit fullscreen mode

If we don't optimize our application, users experience:

  • Slow page loads
  • Laggy interactions
  • Higher bandwidth usage
  • Poor user experience
  • Lower SEO scores

Let's learn how professional React developers solve these challenges.


Why Performance Matters

Imagine a banking application with:

  • Dashboard
  • Transactions
  • Loans
  • Investments
  • Credit Cards
  • Reports
  • Settings
  • Admin Panel

Should all of these be downloaded when the user first opens the login page?

Absolutely not.

Most users haven't even logged in yet.

Loading unnecessary code wastes time and bandwidth.


What is a Bundle?

When React applications are built for production, the source code is bundled into JavaScript files.

  Source Code
      |
      ↓
    Build
      |
      ↓
JavaScript Bundle
      |
      ↓
   Browser
Enter fullscreen mode Exit fullscreen mode

Without optimization:

App.js

5 MB
Enter fullscreen mode Exit fullscreen mode

The browser downloads everything at once.


What is Code Splitting?

Code Splitting divides a large bundle into smaller chunks.

Instead of:

App Bundle
    ↓
   5 MB
Enter fullscreen mode Exit fullscreen mode

We get:

 Home
   ↓
200 KB

Dashboard
   ↓
400 KB

Settings
   ↓
180 KB

Reports
   ↓
500 KB
Enter fullscreen mode Exit fullscreen mode

Only the required chunk is downloaded.

Benefits:

  • Faster initial load
  • Better caching
  • Improved performance

Lazy Loading

Lazy Loading means loading a component only when it's needed.

Instead of loading:

Dashboard
Profile
Reports
Settings
Admin
Enter fullscreen mode Exit fullscreen mode

during startup,

React loads:

          Login
            ↓
   User Clicks Dashboard
            ↓
Download Dashboard Component
Enter fullscreen mode Exit fullscreen mode

This significantly reduces startup time.


React.lazy()

React provides built-in lazy loading.

Example:

import { lazy } from "react";

const Dashboard = lazy(() => import("./Dashboard"));
Enter fullscreen mode Exit fullscreen mode

Notice:

The component is imported only when React needs it.


What is Suspense?

Since lazy-loaded components take time to download, users need feedback.

React provides:

<Suspense>
Enter fullscreen mode Exit fullscreen mode

Example:

import { Suspense, lazy } from "react";

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

<Suspense fallback={<Loading />}>
  <Dashboard />
</Suspense>
Enter fullscreen mode Exit fullscreen mode

Flow:

   Navigate
       ↓
Download Component
       ↓
 Loading Spinner
       ↓
 Component Ready
       ↓
  Display UI
Enter fullscreen mode Exit fullscreen mode

Route-Based Lazy Loading

This is one of the most common optimization techniques.

Instead of:

import Dashboard from "./Dashboard";

import Profile from "./Profile";

import Reports from "./Reports";
Enter fullscreen mode Exit fullscreen mode

Use:

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

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

const Reports = lazy(() => import("./Reports"));
Enter fullscreen mode Exit fullscreen mode

Each page loads only when visited.


Lazy Loading with React Router

Example:

<Routes>
  <Route path="/dashboard" element={
    <Suspense fallback={<Loading />}>
        <Dashboard />
    </Suspense>
  }/>
</Routes>
Enter fullscreen mode Exit fullscreen mode

Users downloading only the pages they visit improves overall application performance.


Dynamic Imports

JavaScript supports importing modules on demand.

Example:

const module = await import("./calculator");
Enter fullscreen mode Exit fullscreen mode

This is useful when loading:

  • Charts
  • Rich text editors
  • PDF viewers
  • Analytics libraries

Only when users need them.


Memoization

Sometimes components re-render unnecessarily.

Example:

Parent
   ↓
Child A
   ↓
Child B
   ↓
Child C
Enter fullscreen mode Exit fullscreen mode

If Parent updates, every child re-renders.

Even if Child C's data never changed.


React.memo()

React.memo() prevents unnecessary re-renders.

Example:

const UserCard = React.memo( 
   function UserCard(){
      return <h2>User</h2>;
  });
Enter fullscreen mode Exit fullscreen mode

Now UserCard re-renders only when its props change.


useMemo()

Expensive calculations should not execute on every render.

Example:

const sortedUsers = useMemo( ()=>{
  return users.sort(...);
},[users]);
Enter fullscreen mode Exit fullscreen mode

Without useMemo(),

sorting happens on every render.

With it,

sorting happens only when users changes.


useCallback()

Functions are recreated during every render.

Example:

const handleClick = useCallback(()=>{
 console.log("Clicked");
},[]);
Enter fullscreen mode Exit fullscreen mode

This is especially useful when passing callbacks to child components.


Debouncing

Imagine a search box.

User types:

R
Re
Rea
Reac
React
Enter fullscreen mode Exit fullscreen mode

Without optimization:

5 API Calls
Enter fullscreen mode Exit fullscreen mode

Better:

User Stops Typing
       ↓
Single API Call
Enter fullscreen mode Exit fullscreen mode

This technique is called Debouncing.

It reduces unnecessary requests and improves responsiveness.


Virtualization

Imagine displaying:

100,000 Rows
Enter fullscreen mode Exit fullscreen mode

Rendering every row is expensive.

Instead:

      Render
        ↓
Only Visible Rows
Enter fullscreen mode Exit fullscreen mode

Libraries such as react-window and react-virtualized help render only the visible portion of large lists.


Image Optimization

Large images slow applications.

Best practices:

  • Compress images
  • Use modern formats (WebP, AVIF)
  • Lazy-load images
  • Serve responsive image sizes

Example:

<img loading="lazy" src="profile.webp"/>
Enter fullscreen mode Exit fullscreen mode

The browser loads the image only when it approaches the viewport.


Bundle Analysis

Large dependencies increase bundle size.

Tools like bundle analyzers help identify:

Charts Library 1.5 MB
        ↓
    Replace
       ↓
300 KB Alternative
Enter fullscreen mode Exit fullscreen mode

Always review large third-party packages before adding them.


Enterprise Example

Consider an internet banking application.

Without optimization:

  Login
   ↓
Download

Dashboard
Loans
Cards
Reports
Settings
Admin
Investments
Enter fullscreen mode Exit fullscreen mode

Total:

6 MB
Enter fullscreen mode Exit fullscreen mode

Optimized version:

         Login
           ↓
        500 KB
           ↓
    User Opens Reports
           ↓
  Download Reports Module
           ↓
  User Opens Investments
           ↓
Download Investments Module
Enter fullscreen mode Exit fullscreen mode

The initial experience becomes much faster.


Measuring Performance

React applications can be measured using:

  • Browser DevTools
  • React DevTools Profiler
  • Lighthouse
  • Core Web Vitals

Key metrics include:

  • First Contentful Paint (FCP)
  • Largest Contentful Paint (LCP)
  • Interaction to Next Paint (INP)
  • Cumulative Layout Shift (CLS)

Monitoring these metrics helps identify performance bottlenecks.


Common Mistakes

1. Optimizing Everything

Not every component needs memoization.

Overusing:

  • React.memo()
  • useMemo()
  • useCallback()

can increase code complexity without measurable benefits.

Optimize only after identifying bottlenecks.


2. Importing Large Libraries Globally

Avoid importing heavy libraries if only one page uses them.

Use lazy loading or dynamic imports instead.


3. Rendering Huge Lists

Always consider virtualization when displaying thousands of items.


4. Ignoring Network Conditions

Users may access your application on slow mobile networks.

Optimize bundle size to improve performance for everyone.


Performance Optimization Checklist

Before releasing your application:

✅ Enable code splitting.
✅ Lazy-load routes and heavy components.
✅ Optimize images.
✅ Use memoization only when necessary.
✅ Debounce search requests.
✅ Virtualize large lists.
✅ Analyze bundle size regularly.
✅ Monitor Core Web Vitals.


Key Takeaways

Today, we learned:

✅ Code splitting reduces initial bundle size.
React.lazy() loads components on demand.
Suspense provides loading feedback for lazy-loaded components.
React.memo(), useMemo(), and useCallback() help avoid unnecessary work.
✅ Debouncing and virtualization improve performance for large applications.
✅ Measuring performance is just as important as optimizing it.


Coming Next 🚀

In Day 26, we will explore:

React Testing – Unit Testing, Integration Testing & End-to-End Testing

We will learn:

  • Why testing matters
  • Jest fundamentals
  • React Testing Library
  • Testing components
  • Mocking API calls
  • User interaction testing
  • End-to-End testing with Cypress and Playwright
  • Enterprise testing strategies

Testing is a critical skill for building reliable, maintainable, and production-ready React applications.

Happy Coding! 🚀

Top comments (0)