All Articles

Optimizing a React Application using Lazy Loading, Caching and Code Splitting

Optimizing the performance of a React application is crucial for providing a smooth and responsive user experience. One way to achieve this is by implementing techniques like lazy loading, caching and code splitting.

Optimizing a React Application using Lazy Loading, Caching and Code Splitting

What is Lazy Loading

Lazy loading is a runtime behavior. It delays the loading of components until they are needed. This reduces the initial bundle size and improves page load times.

React provides built-in support through React.lazy and Suspense.

Example

import React, { Suspense } from "react"

const Settings = React.lazy(() => import("./Settings"))

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <Settings />
      </Suspense>
    </div>
  )
}

Only when the Settings component is rendered will the associated chunk be downloaded.

Advantages

  1. Reduces initial load time: Only loads what’s needed for the current view.
  2. Improves perceived performance: Makes the app feel faster by deferring non-critical components.
  3. Saves bandwidth: Useful on slow networks or mobile devices.

Drawbacks

  1. Delayed content rendering: Some components might appear with a noticeable lag.
  2. Requires fallback UI: You need to handle loading states properly.
  3. More complexity: Managing lazy loading logic across many components can get messy.

Why Code Splitting

Code splitting breaks the app into smaller bundles that can be loaded on demand. This avoids shipping unnecessary code up front.

With tools like Webpack and Vite, you can split code by routes, components, or even libraries.

Route-Based Splitting Example (React Router v6)

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

const Home = lazy(() => import("./pages/Home"))
const About = lazy(() => import("./pages/About"))

function AppRoutes() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </Router>
  )
}

Advantages

  1. Smaller bundle sizes: Breaks the app into smaller chunks, improving load speed.
  2. Faster route-based navigation: Only loads code for the current route.
  3. Better scalability: Large apps benefit from more manageable build sizes.

Drawbacks

  1. Increased number of requests: More files to download (unless HTTP/2 is used efficiently).
  2. Latency for new chunks: First-time navigation to a route might be slower.
  3. Debugging is harder: Source maps are split too, which can complicate debugging in dev tools.

Although Lazy Loading and Code Splitting appear similar, they are in truth 2 different concepts. The often work together but are not to be confused as the same thing.

Concept Code Splitting Lazy Loading
When Build time Runtime
What Splits code into smaller files Loads those files only when needed
How Uses dynamic import() Uses React.lazy / Suspense, etc.
Purpose Reduce bundle size Improve runtime performance
Relation Enables lazy loading Depends on code splitting to work

When To Cache

Caching is the process of storing a copy of data (like files, API responses, or assets) in a temporary location so it can be accessed faster in the future.

Instead of fetching data or reloading resources every time, the app reuses the cached version — saving time, bandwidth, and processing power.

  1. Cached assets load faster because they’re served from local storage or memory.
  2. It reduces server load. Fewer network requests means less work for your backend.
  3. It improves the user experience. Pages feel snappier, especially on repeat visits or slow networks.
  4. It can be used to support offline access. With service workers, caching enables basic app functionality even without internet.

Caching Strategies

Caching isn’t one-size-fits-all. These are the most common strategies, each suited for different scenarios:

  1. Cache First

    How it works: Check the cache. If the resource is found, use it. If not, fetch from the network and cache it.

    Use case: Static assets like images, fonts, or bundled JS/CSS.

    Pros: Fast load times for repeat visits. Works offline.

    Cons: Risk of stale content if not updated.

  2. Network First

    How it works: Try to fetch from the network. If that fails (e.g., offline), fall back to cache.

    Use case: Dynamic data like user dashboards or feeds.

    Pros: Keeps content fresh.

    Cons: Slower than cache-first, and fails without a network if not cached yet.

  3. Stale While Revalidate

    How it works: Serve from cache immediately, then update the cache in the background with fresh data.

    Use case: Pages that should load fast but don’t need to be 100% up to date.

    Pros: Instant response + eventual freshness.

    Cons: Can show outdated data temporarily.

  4. Cache Only/Network Only

    Cache Only: Only load from cache. Good for true offline-first apps.

    Network Only: Always fetch from the server. Used for admin panels or sensitive real-time data.

HTTP vs Service Worker Config

Layer Tool / Method Typical Use
HTTP Caching Cache-Control, ETag, Expires Static assets (CDN or server)
App Caching localStorage, IndexedDB Store API data, auth, settings
Service Worker Workbox, custom fetch handlers Control request/response logic

Final Thoughts

Optimizing web applications doesn’t require major rewrites most times. It’s all about reducing what the browser loads, when it loads it, and how often it reloads the same content. These techniques make your app feel faster without changing the user experience or design.

Published Jan 14, 2023

Hey there! I'm Hemense, a Frontend Developer with 4 years of experience developing scalable, high-performance web applications in SaaS, fintech, ERP, and AI-driven solutions. I am skilled in building component-driven architectures, improving user engagement, reducing churn, and optimizing platform performance for revenue growth. I have experience with React, TypeScript and its ecosystem. I am passionate about data- driven development, design systems, and engineering best practices to drive impactful user experiences.