Tree Shaking
Method
An optimization that removes code from the shipped bundle when it was imported but never actually used.
Why it exists
Screen code written across hundreds of separate files gets packed into a handful of files the browser can fetch efficiently. The tool doing that packing is a bundler, and early bundlers included everything that had been imported. Pull one function out of a date library and the entire library went into the output alongside it.
There was a day at Pagedeck when that accumulated weight became visible. The JavaScript downloaded for the first screen was 1.4 megabytes. Opening it up, an entire icon set was sitting in there, and twelve of its 900 icons were the ones actually used. The date library was in there whole too, with two functions used. For somebody connecting from the subway the first screen took eight seconds to appear, and the page stayed white the whole time they waited.
Tree shaking removes that dead weight. The name comes from the image of shaking a tree until the dry branches fall. Treat the relationships where one file imports another as a tree, start from the root, and keep only the branches that are actually reached. Under frontend, the implementation of what users see and operate, it belongs to the build group, the one that shrinks the output.
How to run it
It isn't one line of configuration; five things have to line up.
- Match the module format. The tool has to be able to decide by reading the code rather than by running it. Use a format that determines what to import at run time and the tool can't tell what's used, so it keeps everything. The code has to be written in the standard module syntax for any of this to work.
- Declare which files do something merely by being imported. Code that changes global settings or injects styles the moment the file is read behaves that way. The tool has no way to know what would break if it removed such code, so it safely keeps all of it. Record in the configuration which files are free of side effects and it can start removing them.
- Import only the names you need. Take a whole set under one name and reach into it with a dot, and from the tool's point of view the entire set is in use. Using twelve icons from a set means writing those twelve names out one by one.
- Look at the result. Attach a tool that draws out what is inside the bundle and how much each part of it weighs. Turn the setting on and move on, and it is common to go on believing the bundle shrank while it sat unchanged the whole time. The reason Pagedeck's icon set was still in there was one line breaking condition 3, and that line only turned up in the picture.
- Put a size ceiling in the build. Make the build fail outright when the first-screen bundle passes a number you set. Shrink it once and, as features keep getting added, it returns to roughly where it was within a few months.
When it fits and when it doesn't
The payoff is largest in screen code that leans on external libraries. Code written in-house usually contains only what's needed, so there's little in it to remove; somebody else's library is mostly made of features you don't use.
The limits are clear.
- It removes only dead code. Code that genuinely runs stays. The problem where users who never open the settings screen still download the settings screen isn't solved this way; code splitting goes there, and lazy loading goes to the images that aren't even visible yet.
- The library has to cooperate. A library shipped as one large pre-packed file offers nothing to cut into. Teams who consider this while choosing libraries suffer less later.
- It's weak against names assembled at run time. Build a function name by concatenating strings and the tool can't tell what will be used, so it keeps all of it. Removing it means rewriting that part to use fixed names instead.
- A wrong declaration deletes code you needed. If a file recorded as side-effect-free actually contained global styles, the layout breaks once it's deployed. Step 2 is a statement of fact rather than a performance setting, so it shouldn't be written without checking.
How it sounds at work
"We use twelve of 900 icons and the whole set is in the bundle."
The way it's imported is what stopped the removal from working at all.
"Let's generate the bundle picture. We turned the setting on and never checked."
Separating believing that it shrank from actually knowing that it did.
"Tree shaking won't drop this — the code is used. It needs splitting instead."
Correcting which of the two methods applies here.
At Pagedeck, Theo records the first-screen bundle size in one line every week. In a week where the number rises, he generates the picture to find what arrived and writes the cause down next to it. The number jumped sharply in a week when Noa added new icons, and the rule about importing icons by name came out of that.
Shrinking the number isn't the goal in itself. The test is whether the first screen that took eight seconds from the subway now takes three.
What people get wrong
The most common position is that turning the setting on handles it, since the bundler's documentation covers it in one line. But this optimization isn't something the tool does alone; four things have to agree — the module format, the side-effect declaration, the way imports are written, and the library's cooperation. Break any one of the four and the tool chooses the safe path and keeps the code. It doesn't announce that it kept anything either, so a team that only turned it on believes for months that a bundle shrank when it didn't.
The other frequent belief is that a smaller bundle means a faster page. The two things are connected without being the same thing. The time until the first screen appears depends on the number of requests and on how long the downloaded code takes to run, not only on how much arrived. Cut 1.4 megabytes down to 900 kilobytes while leaving code that computes for a long time at startup, and nothing about it feels different. So after shrinking, what gets measured again is the time until a user sees the first screen rather than the bundle size, and if that number doesn't move, the cause is somewhere else.
Easily confused with
| Term | What it does | How it differs |
|---|---|---|
| Code splitting | Hands over parts of the code on entering a screen instead of all of it up front | It deals with the code that is actually used. Tree shaking discards code that isn't used at all; splitting defers code that is. Teams end up using both of them together |
Where to go next
Last updated 2026-09-16