How I Reduced My React Bundle Size by 30% (With Real Examples)
Ndeye Fatou Diop

Ndeye Fatou Diop @_ndeyefatoudiop

About: Hi 👋🏽, I am a self-taught Senior Front-End Engineer. I share tips to help overwhelmed junior frontend developers here => frontendjoy.com/ 😻🥳.

Location:
Asnieres-Sur-Seine, France
Joined:
Jul 26, 2020

How I Reduced My React Bundle Size by 30% (With Real Examples)

Publish Date: Apr 7
21 4

📚 Download my FREE 101 React Tips And Tricks Book for a head start.


Ever had this happen?

You start building a new frontend app.

Everything is fast. Production builds take seconds. Users are happy 🥰.

But as time goes on…

  • Build times start to drag.

  • The app feels slower.

  • Users complain.

You’re unsure why — you’ve just been writing “normal” code.

What’s going on?

99% of the time, it’s because your bundle size spiraled out of control.

In this post, I’ll show you 7 proven ways to reduce bundle size and speed up your builds — with a real demo you can try.


⚡️ Before vs After

By the end of this guide, we will go from:

📦 283.39 kB → 198.33 kB

That’s a 30%+ size reduction, just by applying simple tips.


🛠️ Setup

For this demo, I built a React app (generated with V0) available on GitHub:

👉 GitHub repo

Demo Application

Key details:

  • Run npm run build to bundle the app for production.

  • Each optimization step has a dedicated branch (step-1-remove-side-effects, step-2-remove-unused-files-packages, etc.).

  • The main branch contains the unoptimized version, and the fully optimized code is in the step-7-lazy-load-components branch.

💡 Note:

- Each step builds on the previous one: branch step-3-… includes fixes from step-1-… and step-2-…

- I’m using global CSS here (for speed). In real apps, prefer CSS Modules or tools like Tailwind.

Tooling

I used vite-bundle-analyzer to visualize bundle contents.

Here’s the initial bundle from the main branch:

Bundle graph generated on main branch


Step #1: Eliminate Side Effects in Files

👉 Code changes

Bundlers rely on tree-shaking to exclude unused code from the final bundle—unless a file has side effects (see #Benefit 3 in my post about bundlers).

Side effects (like modifying the window object) force the bundler to include the file, even if unused.

🧪 Example:

In our demo, the file HelloWorld.js contains a side effect:

window.someBadSideEffect =
  "I'm a side effect and I will be included inside the bundle even if not used";
Enter fullscreen mode Exit fullscreen mode

Result:

Even though the file isn’t used, its code appears in the bundle file (dist/assets/index-[hash].js).

Fix: Remove the side effect. The file is now excluded from the bundle.

Demo opening the final bundle file and showing the side effect code

Image of the final bundle file without the side effect


Step #2: Hunt Down Unused Files & Packages

👉 Code changes

Unused files/packages usually don’t bloat your bundle—but they:

  • Slow down bundling (more files to process).

  • Risk breaking tree-shaking (if they contain side effects).

Tool Recommendation:

Run npx knip or npx depcheck to detect dead code.

In our demo:

  • HelloWorld.js and lodash-es were flagged as unused.

  • After removing them, the number of modules processed dropped from 44 to 42.

The impact would be more significant in more complex applications, and your app will build even faster ⚡️.

Results of running npx knip

The build results before/after the removal


Step #3: Avoid Barrel Files

👉 Code changes

Barrel files (like src/components/index.js) consolidate exports for cleaner imports:

import { Dashboard, UserManagement, Settings, Clock } from "./components";
Enter fullscreen mode Exit fullscreen mode

But they introduce downsides:

  • Side effects propagate: If any exported file has side effects, the bundler can include it. This is why the side effects were present in Step #1.

  • More files to process: The module bundler will have to process more (or even irrelevant files if someone forgot to delete them). This results in slower builds.

In my demo app, I removed all the barrels in the step-3-remove-barrel-files branch.

Result: The number of modules transformed went from 42 → 37.

The build results before/after removing barrel files


Step #4: Export Functions Directly, Not Objects/Classes

👉 Code changes

When you export an object or class, all its methods are bundled—even unused ones.

In our demo app, the time.js file exports a utility object, but only getTimeInFormat is used. Yet, the entire object landed in the bundle.

Before: The bundle file with the unused methods

Fix: Export functions individually. Now, unused utilities are stripped automatically.

Result: There is a slight decrease in the bundle size.

After: The bundle file without unused functions


Step #5: Swap Heavy Libraries for Lighter Alternatives

👉 Code changes

This is a big one.

In the demo app, I used moment.js.

But if you check Bundlephobia, you’ll see it’s huge.

A better choice? dayjs — smaller and modern.

Swapping moment for dayjs gives you an instant bundle size drop.

Pro tip: Also check for ESM support—it helps with tree-shaking.

Bundle graph with moment.js

Bundle graph with dayjs

The build results before/after removing moment.js


Step #6: Lazy-Load Non-Critical Packages

👉 Code changes

If a package isn’t needed immediately, don’t load it at startup.

Example:

I use Fuse.js for fuzzy search, but only when the user starts typing.

Solution:

Instead of importing it statically, I load it when needed using dynamic imports:

// Lazy load Fuse.js
const Fuse = import("fuse.js").then((module) => module.default);
Enter fullscreen mode Exit fullscreen mode

Result: fuse.js splits into a separate chunk, reducing the initial load.

Before: Bundle graph with fuse.js used directly

After: Bundle graph with fuse.js imported dynamically

The build results before/after importing fuse.js dynamically


Step #7: [React] Lazy-Load Non-Critical Components

👉 Code changes

Same idea — if components aren’t needed at startup, don’t load them immediately.

In my demo, files like Dashboard.jsx and Settings.jsx are only required when the user clicks a button.

So I lazy-load them using React.lazy:

const Settings = lazy(() =>
  import("./components/Settings").then((module) => ({
    default: module.Settings,
  }))
);
Enter fullscreen mode Exit fullscreen mode

Result: A smaller initial bundle, which results in faster first load of your app.


Bonus 🪄

A few more ideas you could explore:

  • Add "sideEffects": false in package.json to improve tree-shaking

  • Use Bundlewatch or Size Limit to monitor size in CI

  • Enable more code-splitting or compression with Vite plugins


✅ Cheatsheet

Quick recap of everything we did:

✅ Remove side effects from files  
✅ Delete unused files and packages  
✅ Avoid barrel files  
✅ Export functions directly  
✅ Use smaller libraries (dayjs > moment)  
✅ Lazy-load heavy packages  
✅ Lazy-load components 
Enter fullscreen mode Exit fullscreen mode

👉 Code changes


Summary

Frontend apps often get slower over time — even without adding “heavy” code.

But if you apply these tips regularly, you can stay fast, keep build times short, and make life easier for your team and users.

Got other tips? I'd love to hear them 🙂.

Section Divider

That's a wrap 🎉.

Leave a comment 📩 to share more tips.

And don't forget to drop a "💖🦄🔥".

If you're learning React, download my 101 React Tips & Tricks book for FREE.

If you like articles like this, join my FREE newsletter, FrontendJoy.

If you want daily tips, find me on X/Twitter or Bluesky.

Comments 4 total

  • Nevo David
    Nevo DavidApr 7, 2025

    Great tips to improve React app performance! What's your favorite optimization technique to keep your codebase efficient?

    • Ndeye Fatou Diop
      Ndeye Fatou DiopApr 8, 2025

      Super glad you like it :). I like to use as few dependencies as possible and use small ones 😊

  • Anmol Baranwal
    Anmol BaranwalApr 8, 2025

    Super useful. I love that you included resources and before/after snapshots.

Add comment