📚 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:
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 fromstep-1-…
andstep-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:
Step #1: Eliminate Side Effects in Files
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";
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.
Step #2: Hunt Down Unused Files & Packages
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
andlodash-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 ⚡️.
Step #3: Avoid Barrel Files
Barrel files (like src/components/index.js) consolidate exports for cleaner imports:
import { Dashboard, UserManagement, Settings, Clock } from "./components";
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.
Step #4: Export Functions Directly, Not Objects/Classes
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.
Fix: Export functions individually. Now, unused utilities are stripped automatically.
Result: There is a slight decrease in the bundle size.
Step #5: Swap Heavy Libraries for Lighter Alternatives
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.
Step #6: Lazy-Load Non-Critical Packages
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);
Result: fuse.js
splits into a separate chunk, reducing the initial load.
Step #7: [React] Lazy-Load Non-Critical Components
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,
}))
);
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
inpackage.json
to improve tree-shakingUse 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
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 🙂.
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.
Great tips to improve React app performance! What's your favorite optimization technique to keep your codebase efficient?