In short
Measure before cutting: a bundle analyser will usually show that two or three dependencies account for most of the weight. The largest returns come from removing or replacing heavy libraries, splitting code so each route loads only what it needs, and deferring anything not required for the first interaction. Parsing and executing JavaScript costs more on a mid-range phone than downloading it does.
Why size matters more than it seems
Download time is the obvious cost and the smaller one. The larger cost is parse and execute, which happens on the device.
On a fast laptop, 500KB of JavaScript is nothing. On a mid-range phone — which is what a large share of your visitors use — the same bundle can occupy the main thread long enough that the page looks ready and does not respond. That gap between rendered and interactive is where people tap and nothing happens.
Which is why the right target is not a byte count. It is: how long until this page does what it looks like it can do?
Measure first
Do not start cutting. Start looking.
Run a bundle analyser for your build tool. It produces a treemap of what is actually in the output, and the result is almost always the same shape: two or three dependencies account for most of the weight, and one of them is usually something you forgot you added.
Also check whether you are shipping the same library twice at different versions, which happens easily through transitive dependencies and is invisible without the analyser.
Then check the real-world number in your field data rather than a lab score, since that reflects the devices your visitors actually hold.
The reductions that pay
In rough order of return.
Remove or replace heavy dependencies. The biggest single win, usually. A date library, a charting package, an icon set imported wholesale, a utility library where you use four functions. Check the size before adding anything, and check what already exists in the platform — a great deal of what libraries provided a few years ago is now built into browsers and standard APIs.
Import only what you use. Importing a whole library for one function relies entirely on tree-shaking working, and tree-shaking silently fails on packages that are not structured for it. Named imports from a module that supports them; direct submodule imports where it does not.
Split by route. A visitor on your pricing page should not download the code for your dashboard. Route-level splitting is usually a configuration change with a large payoff.
Defer what is not needed immediately. Modals, editors, video players, analytics, chat widgets, anything below the fold. Load on interaction or on idle. A chat widget that loads on first click rather than on page load is a common quick win.
Drop polyfills you no longer need. Build targets are frequently set once and never revisited, so bundles carry support for browsers nobody uses. Check your actual visitor data and set the target accordingly.
Ship modern syntax. Transpiling to an old target inflates output substantially. Serve modern JavaScript to modern browsers.
Third-party scripts are usually the real problem
A point worth making bluntly: on many sites the first-party bundle is well optimised and the page is still slow, because third-party scripts dwarf it.
Analytics, tag managers, chat widgets, heat-mapping, A/B testing, ad and consent scripts. Each one is added by a different person for a good reason, and nobody owns the total.
Audit them. For each, ask what it is for, who looks at the data, and what it costs. A tag manager that lets marketing add scripts without review is convenient and is also an uncapped performance budget.
Where they must stay: load them after the page is interactive, use async or defer deliberately, and self-host what you can to avoid extra connections.
Loading strategy
defer for anything that touches the DOM but is not needed before render. It downloads in parallel and executes in order after parsing.
async for independent scripts with no dependencies and no ordering requirement.
Preload only what is critical. Preloading everything defeats prioritisation and can make things slower.
Watch for long tasks. A single script blocking the main thread for hundreds of milliseconds hurts responsiveness more than several small ones totalling the same time. Break up long work.
Do you need this much JavaScript
The uncomfortable question, and often the most valuable.
Plenty of pages ship a framework and a client-side router to render content that does not change. Marketing pages, articles, documentation — these can be static HTML with a small amount of script for the interactive parts.
This is not an argument against frameworks, which earn their weight in genuinely interactive applications. It is an argument for noticing when a page is a document rather than an app, and for not needing a framework on the pages that are documents.
Keeping it from creeping back
Bundle size regresses gradually, one reasonable addition at a time.
Set a budget and enforce it in CI, so a pull request that adds 200KB fails rather than merges. This is the single most effective control, because it makes the cost visible at the moment of the decision.
Report size changes in pull requests. A number in the review turns an invisible cost into a conversation.
Re-run the analyser periodically. Dependencies grow, and transitive additions arrive without anyone choosing them.
If your pages render fast and feel slow to use, that gap is usually JavaScript — book a call and we will find where it is going.
Common questions
How do I find what is making my bundle large?
Run a bundle analyser for your build tool, which produces a treemap of what is actually in the output. Almost always two or three dependencies account for most of the weight, and one is something nobody remembers adding. It will also reveal duplicate copies of a library at different versions.
Why does JavaScript size matter more than image size?
Because JavaScript must be parsed and executed on the device, not just downloaded. On a mid-range phone that work occupies the main thread, so the page can look ready while not responding to taps. Images are heavy but do not block interactivity in the same way.
What is the fastest way to reduce bundle size?
Remove or replace the heaviest dependencies, then split code by route so each page loads only what it needs, then defer anything not required for the first interaction — modals, editors, chat widgets, analytics. Those three usually account for most of the achievable reduction.
Are third-party scripts part of the problem?
Frequently they are the larger part. Analytics, tag managers, chat widgets, heat-mapping and consent scripts each get added by a different person for a good reason, and nobody owns the total. Audit what each is for and who actually looks at the data before optimising your own code further.
How do you stop bundle size creeping back up?
Set a size budget and enforce it in CI so a pull request exceeding it fails rather than merges, and report size changes in the pull request so the cost is visible at the moment of the decision. Re-run the analyser periodically, since transitive dependencies grow without anyone choosing them.
