Master bundle size optimization by auditing your dependency graph. Learn to strip unused code, use subpath imports, and enforce performance budgets today.
Last month, I spent about three days chasing a 400KB regression in our main dashboard bundle. It turned out that a single utility library, imported via a barrel file, was pulling in half the React ecosystem despite us only using a single formatDate function.
If you’ve ever felt like your app is getting heavier despite writing "clean" code, you’re likely fighting the same ghost. Achieving consistent bundle size optimization isn't just about deleting unused imports; it's about understanding how your bundler interprets your dependency graph.
Most of us assume that modern bundlers like Webpack 5 or Vite will automatically handle tree-shaking for us. In theory, they perform static analysis to remove code that isn't exported or referenced. In practice, they are often blocked by side effects.
If a package has a sideEffects property set to true in its package.json—or worse, lacks the field entirely—the bundler must assume that importing the file might change global state. It won’t prune anything. To verify if your dependencies are playing along, I start by running webpack-bundle-analyzer or the rollup-plugin-visualizer.
When you see a giant block for a library you barely use, check these two things:
We once tried to fix a massive bundle by switching to barrel files, thinking it would make imports cleaner. We were wrong. Barrel files often force the bundler to load every export in the module, effectively killing our tree-shaking efforts.
Instead of importing from the root, I’ve moved toward subpath imports. This forces the bundler to look at a specific, smaller file rather than the entire package index.
Instead of this:
JAVASCRIPTimport { heavyUtility } from CE9178">'large-library';
Try this:
JAVASCRIPTimport heavyUtility from CE9178">'large-library/dist/heavyUtility';
This simple change often saves roughly 150KB of minified JS. If the library doesn't expose subpaths, you might need to use a tool like babel-plugin-import or vite-plugin-imp to transform those imports automatically.
| Strategy | Tree-Shaking Potential | Complexity | Impact on Bundle |
|---|---|---|---|
| Root Import | Low (if side effects) | Low | High Bloat |
| Barrel Files | Very Low | Medium | Very High Bloat |
| Subpath Imports | High | Medium | Minimal |
| Direct ESM | Highest | High | Optimal |
When I perform a dependency analysis, I follow a strict process. I don't just look at the total KB; I look at the "cost" of the dependency relative to its utility.
Flow diagram: Run Bundle Analyzer → Large Chunk Found?; B -- Yes → Check Package Source; Check Package Source → ESM Supported?; D -- No → Find Smaller Alternative; D -- Yes → Implement Subpath Import; Implement Subpath Import → Verify Bundle Reduction
If you want to automate this, look into Performance budgets: Enforcing Bundle Size and Vital Thresholds. It prevents regressions from creeping back in after you’ve done the hard work of cleaning up. I also recommend referencing Cutting JavaScript bundle size: A practical guide for developers to see how these techniques scale across larger codebases.
How do I know if a library is tree-shakeable?
Check the package.json for the sideEffects: false flag. If it’s missing, test it by importing a single function and checking the bundle output size.
Are subpath imports always safe?
Not always. Some libraries use internal file structures that change between minor versions. Always pin your dependencies in package.json if you rely on deep imports.
Does this help with INP? Indirectly, yes. Smaller bundles mean faster parsing and compilation, which reduces the time the main thread spends blocked. For more on that, read about INP Optimization: Strategies for Reducing Long Tasks.
JavaScript performance is a moving target. What works today might break with a package update tomorrow. I’ve stopped trying to reach "perfect" bundle sizes and instead focus on maintaining a budget that forces me to be intentional about every kilobyte I add. Next time, I’m planning to experiment with Module Federation to offload parts of our app entirely, but for now, subpath imports keep our users happy.
Adaptive Loading using Client Hints and the Network Information API helps you deliver faster experiences. Learn how to tailor assets to real user conditions.
Read moreWeb Performance starts with a responsive main thread. Learn how to use Web Workers and MessageChannel to offload heavy tasks and improve your Core Web Vitals.