Every JavaScript byte the browser must parse and execute is a bill charged to the main thread, and the main thread is where INP, LCP text and startup interactivity all live.
The bundle problem is rarely one big file. It is the accumulation of dependencies, the code that loads on every page but runs on few, and the framework boot that happens before the first interaction can.
This guide covers splitting by route, deferring what the route does not need, and the removal pass that stops the bundle from growing back.
Measure the Boot Cost
Before cutting anything, measure what actually runs. The boot cost is execution time plus parse, and both show up in the performance trace of a throttled device.
The budget that matters is main thread time to first interaction, not the download size. A 90 percent gzipped saving with 80 percent execution cost saved is a rounding error for users on midrange phones.
Instrument per route. The homepage boots one set of modules, the product page another, and the audit should see each route's own bill.
Split by Route and Interaction
Route-based splitting loads only the code the current page needs. A multi-page site with one shared bundle carries every page's cost on every page; the split fixes exactly that.
Interaction-based splitting defers the modules that only matter when a user acts. The search component, the chart, the rich filter, they load on demand instead of at boot.
The shared vendor chunk deserves the same attention. A framework core that every route needs is honest shared cost, but a dependency used by one page does not belong in it.
| Level | Loads When | Fixes |
|---|---|---|
| Route split | Page boots | Removes unrelated page cost |
| Interaction split | User acts | Keeps boot lean |
| Vendor chunk | Shared across routes | Controls honest shared cost |
| Entry trimming | Never | Removes dead and unused code |
The Removal Pass
Tree shaking removes exported code nothing imports, but it cannot remove what a library conditionally includes. The reliable removal pass is the import audit, every imported module earns its bytes.
Bundle analyzers show where the size actually comes from. A utility library pulling in date handling, a UI kit importing all icons, these show as obvious wins.
The discipline is recurring. Dependencies accumulate in slow motion, and the quarterly removal pass keeps the bill from silently compounding.
Bundle Optimization Failures
Optimizing download size while ignoring execution is the first failure. Users pay the execute tax regardless of how small the transfer was.
Over-splitting is second. Fragmented entry points and dozens of tiny requests add their own network and cache overhead, and the gain disappears.
The framework switch is third. Rebuilding in a smaller framework to save bytes, while importing the same heavyweight dependencies, trades a bundle for a migration bill.
- Caring about transfer size alone
- Splitting until request overhead eats the gain
- Framework swaps without auditing the real cost
- No quarterly dependency audit
The Boot Budget System
Set a main thread boot budget per route and fail the build when exceeded. The budget turns every dependency decision into an explicit trade instead of an accident.
Split by route and interaction, then trim imports, then verify on a throttled phone. The order matters because splitting exposes what trimming should remove.
Re-measure quarterly and compare against the budget. Bundles grow back without review, so the quarterly pass is the enforcement.
Real-World Considerations
Ad and tag scripts are part of the main thread bill even though they are not in your bundle. The third-party audit from this series shares the same budget.
Edge rendering shifts where some work runs. Moving server-side work to the edge changes the client contract, so bundle expectations update with the architecture.
Inlining critical JS is a legitimate token, but only for what the first paint needs. Everything else belongs in the deferred path.
Where the Wins Are
Interactive and app-like surfaces pay first. Dashboards, search, filters and product configurators carry real execution cost, so their bundling decisions move the field score.
Read-heavy marketing pages run a lighter pass, split the route, defer the interactive slots, and trim imports quarterly.
Implementation Checklist
- Measure boot cost on a throttled phone per route
- Split bundles by route and interaction
- Audit imports and remove dead dependencies
- Set a main thread boot budget per route
- Fail the build when the budget is exceeded
- Re-audit quarterly
Final Thoughts
The bundle is the main thread bill, and the bill is paid at boot and at interaction. Split what the page needs, defer what the user triggers, and remove what nobody runs.
Measure the execution cost, budget the route, and review quarterly. The bytes follow the discipline.
Frequently Asked Questions
There is no safe size, only a safe budget. Target a main thread boot under the interaction threshold on a throttled device and let execution time, not kilobytes, set the limit.
A CDN speeds delivery, not execution. Parse and run cost the same, so static hosting plus splitting and trimming is the real fix.
Because dependencies accumulate without review. The quarterly import audit is what holds the line, not the one-time optimization.

