
What tree shaking actually is
Tree shaking is dead-code elimination for JavaScript bundles: your build tool walks the dependency graph, figures out which exports are actually imported and used, and drops the rest before it ships to the browser. Get it right and users download less JavaScript, which is why it lands on an SEO team's radar at all — it's a performance lever that feeds Core Web Vitals, not a ranking factor in its own right.
The name comes from imagining your code as a tree: the branches nobody reaches are shaken loose and fall away. A bundler like Rollup, esbuild, or webpack keeps the leaves your app touches and discards the ones it doesn't. The catch is that it only works when the bundler can prove a chunk of code is unused, and that proof depends on how your code and your dependencies are written.
A real example
Say you import a single helper from a utility library:
// You wrote this:
import { formatDate } from 'date-utils';
// date-utils also exports formatCurrency, parseISO,
// diffInDays, and 40 other functions you never call.With proper tree shaking, only formatDate and whatever it depends on ends up in your bundle. The other 43 functions get shaken out. Without it — because the library ships CommonJS, or you imported the whole namespace — you ship all 44 functions to every visitor. On a real site that's the difference between a 12 KB import and a 180 KB one, and that weight shows up in your Total Blocking Time and Largest Contentful Paint.
What tree shaking needs to work
Tree shaking is not automatic magic. It has hard prerequisites, and skipping any one of them quietly disables it.
| Requirement | Why it matters | What breaks it |
|---|---|---|
ES modules (import/export) | Static structure lets the bundler analyze imports at build time | CommonJS (require/module.exports) — resolved at runtime, can't be analyzed |
| Named imports | Bundler can see exactly which exports you touch | import * as utils — forces the whole namespace to stay |
"sideEffects": false in package.json | Tells the bundler unused modules are safe to drop entirely | Missing flag — bundler keeps modules "just in case" they mutate global state |
| Production mode / minification | The dead-code removal pass actually runs and strips flagged code | Dev builds — tree shaking is marked but not applied |
| Dependencies shipping ESM builds | A library only shakes if it exposes an "module" or "exports" ESM entry | Packages that publish only CommonJS bundles |
How to check whether your bundle is being shaken
- Run a bundle analyzer. For webpack use
webpack-bundle-analyzer; for Vite/Rollup userollup-plugin-visualizer. You get a treemap of what's actually in your bundle. - Look for whole libraries you only used once. If you called one Lodash function and the analyzer shows all of Lodash, tree shaking failed for that package.
- Confirm you're reading a production build. Dead code is only removed in production mode. Analyzing a dev build tells you nothing useful.
- Check the dependency's package.json. Look for a
"module"or"exports"field pointing at an ESM build and a"sideEffects"declaration. No ESM entry means it can't be shaken. - Measure the payoff, not the config. Run the page through PageSpeed Insights or Lighthouse before and after and watch the JavaScript transfer size, TBT, and LCP. That's the number that reaches search.
Common mistakes and how to fix them
- Barrel-file namespace imports.
import * as _ from 'lodash'drags the entire library in. Fix: import the exact function —import debounce from 'lodash/debounce'— or uselodash-es, which ships real ES modules. - Assuming a CommonJS dependency will shake. It won't, no matter how your app is configured. Fix: prefer the ESM build of a package (many ship both), or find a lighter alternative.
- A missing or wrong
sideEffectsflag. Set it tofalseglobally, or list the specific files with side effects (a global CSS import, a polyfill) so everything else can be dropped. Setting it wrong strips code your app actually needs at runtime. - Transpiling ESM down to CommonJS before bundling. If Babel or TypeScript is configured to emit CommonJS, you destroy the static structure the bundler relies on. Fix: target
esnext/module: "esnext"and let the bundler handle module output. - Treating tree shaking as the whole job. It removes unused code, not code you use but don't need yet. Pair it with code-splitting and lazy loading so heavy features load on demand.
Frequently asked questions
Does tree shaking help SEO directly?
No. Google doesn't reward tree shaking as a signal. It helps indirectly by cutting JavaScript weight, which improves load and interactivity metrics. Faster pages are the thing search actually cares about.
Why isn't my tree shaking removing anything?
The usual culprits, in order: you're analyzing a dev build, the dependency ships CommonJS, you used a namespace import, or the package is missing a sideEffects declaration so the bundler keeps everything defensively. Work down that list.
Is tree shaking the same as minification?
No. Minification shortens variable names and strips whitespace in code you keep. Tree shaking decides which code to keep in the first place. They run together in a production build but do different jobs.
Do modern frameworks do this for me?
The bundlers behind Next.js, Nuxt, SvelteKit, and Vite tree-shake by default — but only for code that meets the prerequisites. A CommonJS dependency or a sloppy import still ships dead weight regardless of the framework.
What's the fastest win if I've never checked?
Run a bundle analyzer on a production build and hunt for one oversized library you barely use. Swapping a namespace import for a named one, or a CommonJS package for its ESM twin, is often a five-minute change that shaves real kilobytes.
Where this fits
Tree shaking is one piece of shipping less JavaScript. If your analyzer shows dead code that shouldn't be there, the follow-up is usually to remove unused JavaScript and audit your third-party scripts. For the broader picture of how bundles affect rendering and indexing, see the JavaScript SEO technical guide, and to measure the payoff, work through the Core Web Vitals optimization guide.
Claude Vincent is a technical SEO consultant focused on crawlability, rendering, and AI-search visibility. He writes the field guides and case studies at SEO ProCheck, with a bias toward the durable, unglamorous work that decides whether search engines and AI answer engines can actually read and cite a site.
About SEO ProCheck
Technical SEO consulting and GEO strategy with 20 years of enterprise experience. Case studies, resources, and tools for search and AI visibility.
Work With Me
Technical SEO audits, GEO strategy, site migrations, and international SEO. Hourly consulting for teams who need hands-on support, not just reports.







