DEV Community

Cover image for Your Bundle is Lying to You: Hidden Costs of Tree-Shaking
Abhinav Shinoy
Abhinav Shinoy

Posted on

Your Bundle is Lying to You: Hidden Costs of Tree-Shaking

We’ve all heard it:

“It’s fine, unused code gets tree-shaken out.”

But here’s the truth…

Your bundle is lying to you.
And tree-shaking isn't the silver bullet you think it is.

In this post, we’ll look at how tree-shaking really works, what gets left behind, and why your production bundle might be heavier than it needs to be — even when everything looks optimized.


🧩 What Is Tree-Shaking (Really)?

Tree-shaking is a bundler optimization technique that removes unused exports from ES modules during bundling.

Example:

// utils.js
export function used() {}
export function unused() {}
Enter fullscreen mode Exit fullscreen mode

If only used() is imported elsewhere, a good bundler will drop unused() from the final build.

Sounds great, right? So what’s the problem?


🚨 Problem 1: Side Effects Are Sticky

Many libraries contain side effects — things that run when the file is imported, even if nothing is used.

// file.js
console.log('This runs no matter what');
export const something = 123;
Enter fullscreen mode Exit fullscreen mode

Tree-shaking won’t remove this file unless the bundler knows it's side-effect-free.

If the library doesn’t declare:

"sideEffects": false
Enter fullscreen mode Exit fullscreen mode

…or it contains CSS imports, polyfills, or runtime setup, the whole file stays in — even if you use nothing from it.


🚨 Problem 2: Misleading Import Patterns

Some APIs are hard to shake because of how you import them.

Example:

// BAD
import * as _ from 'lodash';
Enter fullscreen mode Exit fullscreen mode

This pulls in the entire lodash bundle — even if you use just one function.

Instead, you should:

// GOOD
import debounce from 'lodash/debounce';
Enter fullscreen mode Exit fullscreen mode

Even better? Use modern tree-shakable alternatives like lodash-es.


🚨 Problem 3: Transpilation Can Break It

Your code might be perfectly optimized, but your build toolchain might sabotage it.

For example:

  • Babel + CommonJS modules = 💣
  • TypeScript with incorrect module targets = 🔥

Tree-shaking only works on ES modules (ESM). If your transpiler converts ESM to CommonJS (require()), the bundler can’t see what’s unused.

🛠 Fix: Set module to "esnext" and let your bundler handle module format.


🚨 Problem 4: You’re Still Paying for Parse and Eval

Even if dead code is dropped from the final JS file, you may still load, parse, or evaluate more than you think.

For example:

  • Large components wrapped in React.lazy() might be excluded from initial render… but still downloaded eagerly.
  • Some lazy-loaded code gets preloaded via <link rel="preload">.
  • Vendors like analytics or error tracking may import large polyfill chunks behind the scenes.

📦 The bundle size might look small — but your browser knows the truth.


🚨 Problem 5: Third-Party Packages Lie Too

That one dependency you installed? It might say it’s tree-shakable — but dig into the source and you’ll find:

  • Mixed ESM + CommonJS output
  • Side effects galore
  • Index files that re-export everything

Always test the actual output, not just what the docs claim.


✅ How to Fight Back

Here’s how to take control of your bundle:

🔍 1. Analyze It

Use tools like:

…and see what’s really in your output.


✂️ 2. Use ESM-Only Libraries

Prefer packages that:

  • Offer native ES module builds
  • Mark "sideEffects": false in their package.json

Bonus points for tree-shakable named exports.


📦 3. Import Precisely

Avoid star imports or named imports from index barrels.

import { format } from 'date-fns'
🚫 import * as dateFns from 'date-fns'


🧪 4. Test with Production Builds

Dev builds lie even more than your bundles.
Always test with minified, production-mode builds.

You’ll often be surprised.


💡 Final Thoughts

Tree-shaking is a great tool — but it’s not magic.

It doesn’t protect you from:

  • Bad imports
  • Lazy-but-eager modules
  • Bloated dependencies
  • Toolchain quirks

So next time your Lighthouse score drops or your TTI climbs, don’t assume your bundler saved you.
Open the analyzer. Inspect the output. Trust nothing.

Because sometimes…
Your bundle is lying to you.


Top comments (12)

Collapse
 
nevodavid profile image
Nevo David

yeah this is honestly spot on, every time i think the build is clean something sneaks in - you think there's a way to really trust your bundle or is it just endless checking?

Collapse
 
abhinavshinoy90 profile image
Abhinav Shinoy

Right? It always feels like a game of whack-a-mole 😅
I don’t think it ever gets to set-it-and-forget-it — but setting up a visualizer in your CI (like source-map-explorer or vite-plugin-visualizer) helps catch sneaky changes early.
Not perfect, but way better than chasing surprises after deploy.

Collapse
 
fstrube profile image
Franklin Strube

Great article. Thanks for sharing!

Collapse
 
abhinavshinoy90 profile image
Abhinav Shinoy

Thanks @fstrube !

Collapse
 
javascriptwizzard profile image
Pradeep

Great post @abhinavshinoy90 !

Collapse
 
abhinavshinoy90 profile image
Abhinav Shinoy
Collapse
 
dotallio profile image
Dotallio

So true about side effects sneaking into bundles, even when you think you’ve done everything right. What’s the sneakiest thing you’ve found lurking in your own builds?

Collapse
 
abhinavshinoy90 profile image
Abhinav Shinoy

One time it was a datepicker that pulled in all of Moment.js, just from one import 😬
We thought we were safe, but it added 200KB+ until we caught it in the analyzer!

Collapse
 
nathan_tarbert profile image
Nathan Tarbert

Pretty cool, honestly makes me second guess how much I rely on my build tools without actually checking what's in there

Collapse
 
abhinavshinoy90 profile image
Abhinav Shinoy

Totally get that — build tools are powerful, but they can lull you into a false sense of security. A quick peek at the bundle now and then can be super eye-opening!

Collapse
 
vetriselvan_11 profile image
vetriselvan Panneerselvam

Great article

Collapse
 
abhinavshinoy90 profile image
Abhinav Shinoy