DEV Community

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

Posted on • Originally published at frontendjoy.com

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

πŸ“š 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.

Top comments (4)

Collapse
 
nevodavid profile image
Nevo David

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

Collapse
 
_ndeyefatoudiop profile image
Ndeye Fatou Diop

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

Collapse
 
anmolbaranwal profile image
Anmol Baranwal

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

Collapse
 
_ndeyefatoudiop profile image
Ndeye Fatou Diop

Glad you like it 😊