Bundle analyzer: finding the cause of a bloated bundle
Typical picture: a product evolves, capabilities grow — and the initial bundle grows faster than the functionality itself.
The first screen takes longer to load, on a weak mobile connection the user leaves before rendering, and the bounce rate on entry to the app grows along with the amount of downloaded code.
The causes are almost never on the surface.
In the source code there are neat modules and small components, while the built bundle ends up with two versions of the same library that arrived transitively; a heavy utility of which one function is used; full sets of locales and polyfills needed by only part of the audience. This is not visible from the code: the size grows not where you made changes.
Bundle analyzer answers a different question — not “it got bigger,” but “what exactly takes up the share and who pulled it in.” The tool builds a module map where the area of a block is proportional to its contribution to bundle size, and next to your codebase you find dependencies you did not write.
webpack-bundle-analyzer and source-map-explorer approach the task from two sides: the first shows the structure of the built chunks and the relationships between them, the second maps the finished file back to the original modules using source maps.
Together they separate a swollen dependency from your own logic, and at the same time find duplication.
Then build optimization begins: route-based splitting, moving rarely used blocks into lazy loading, selective imports instead of whole libraries, replacing heavy packages with lightweight alternatives.
The order of work is suggested by the map itself — first what accounts for the largest share, because that is where load time decreases most noticeably. We explain how this works and what the product gets as a result.
What bundle optimization delivers: speed, conversion, support
Bundle optimization begins with answering the question of what exactly goes to the browser.
Tools like bundle analyzer show the module tree and the weight of each branch, so decisions are made based on numbers: where dependencies are duplicated, where an entire library is included for one function, and what is unused in the production version.
What the client sees: speed, Core Web Vitals, conversion
We remove duplicate dependencies, split code by routes, move rarely needed items into separate chunks and load them on demand.
The volume of transferred files drops by a factor of 1.5–3, first render on mobile speeds up by 30–60%, and Core Web Vitals move into the green zone instead of balancing on the edge.
The connection to conversion is direct: every extra loading step loses some visitors, and winning them back is more expensive than carefully staging the loading once. Forms, catalog, and cart render before the user leaves for a competitor.
What the team sees: server and support
Fewer bytes served means lower load on the server and channel, static assets stay in cache longer, and peak requests pass without expanding capacity.
Support becomes simpler: module boundaries are visible, weight growth is noticeable on every release, and a regression from a single package is found in minutes rather than a day of searching.
Every release leaves a report on the build composition, so weight does not grow unnoticed, and disputed decisions are settled with numbers.
Methods of bundle analysis and optimization: comparison
The visual build map and the source map answer the same question differently. The first shows what takes up space in the module graph, the second shows what actually shipped to the user after optimization. That is why they are used together rather than choosing one instead of the other.
Visual build analysis
webpack-bundle-analyzer reads build stats and draws the hierarchy chunk → module → dependency: this is a fast way to understand why the bundle grew after the latest change.
The limitation is that the map reflects the state before final optimization, and code removed by tree shaking is still visible in it as extra.
| Option | When it fits | Limitations |
|---|---|---|
| Visual build map | Regular checks after releases, finding large dependencies | State before final optimization: removed code looks like extra |
| Source map analysis | Analyzing the production build: what was delivered to the user | Requires correct source maps, chunk structure is not visible |
| Manual dependency audit | Suspicion of duplicate versions and heavy transitive packages | Slow, does not scale without build automation |
Source map and dependency audit
source-map-explorer calculates the contribution of each source module to the finished artifact — only this way can you see that removing unused code actually worked.
Manual dependency audit covers the rest: traversing the package tree reveals several versions of the same library and transitive modules that pull in the entire package.
The workflow is a visual map as a quick snapshot, source map for testing hypotheses, and audit where the cause is not obvious.
How we perform bundle optimization: work stages
Optimization proceeds from measurements, not from taste-based edits to the build configuration. Until there is a dependency map and loading numbers, any change remains a guess that can neither be defended nor repeated.
Audit and optimization plan
We capture metrics on a build close to production: size of the main and deferred chunks, share of third-party libraries, time to first render on a mobile network.
Then we build a dependency map with bundle analyzer and compare it with source-map-explorer — the latter shows source modules, not just packages, and it is on this discrepancy that duplicates of one library’s versions surface.
The optimization plan is a list of edits sorted by how much each one removes from the critical loading path.
- Bundle audit: we look for duplicates, dead code, and libraries included entirely for one function.
- Prioritization: we sort findings by effect and cost of the edit — the plan includes what gives a measurable result.
- Implementation: we split the build into chunks, move heavy modules into lazy loading, and replace unjustifiably large dependencies.
- Step-by-step control: after each edit we compare metrics against the baseline and roll back anything that did not remove the regression.
- Result verification: a run on a test environment and a before/after comparison by chunk size and time to first render.
- Handover: rules in the configuration and thresholds in build checks so the size does not grow back.
The output is not a one-time edit but a managed loading size: a dependency map that the development team understands, and thresholds that catch regression at the build stage rather than in production.
What you receive after optimization
After optimization, the team is left not with “it got faster” but with a set of artifacts by which the build state can be checked and reproduced.
The bundle report and dependency map sit next to the code, the configuration is described in words, and support rules are fixed in guidelines — the result lasts longer than one release and does not fall apart with subsequent edits.
What remains in the project
- Bundle report — bundle analyzer snapshots before and after edits: module volume, initial load composition, extracted chunks.
- Dependency map — a graph of connections marked with duplicates and libraries that are pulled in by two versions at once.
- Updated build configuration — rules for chunk splitting, compression, removal of unused code, dynamic loading points.
- Comments on parameters — what each mode provides and what breaks if it is removed.
What we document in the guidelines
- The threshold for initial load volume after which the build is considered degraded.
- The command to run the analyzer before release and the location where the report is stored for comparison.
- Documentation of the loading architecture: which parts of the application load immediately and which on demand.
- Instructions for a new engineer: where to open the report and how to distinguish normal volume growth from regression.
Such a set removes the main support problem — knowledge stays in the repository, not in one developer’s head. Degradation can be checked in a single run, and an unsuccessful change can be rolled back using a specific list of extracted chunks rather than trial and error.
Case study: reducing bundle size and speeding up loading
The project is an online store with a catalog of several thousand items.
The bundle grew from release to release: the first screen took about 2.9 MB of compressed code, time to interactive on an average mobile device hovered around 6 seconds, and some visitors left before the catalog rendered.
What the analysis showed
We built a bundle map with webpack-bundle-analyzer and compared it with source maps via source-map-explorer.
The picture turned out to be typical: two copies of the routing library, a full date package for two formatting functions, and the entire catalog with filters in one chunk together with the home page code.
From the source, it was visible that unreachable code accounted for about a third of the total volume.
What we changed
Heavy routes — product page, checkout, account — were moved to dynamic import, and some widgets are loaded on user action. Duplicates were reduced to one copy, and instead of the full date package, selective functions were connected.
Shared vendor code was moved into a separate chunk with long-term caching so it would not be re-downloaded on every release.
As a result, the bundle was reduced from 2.9 MB to 980 KB, the critical path for the first screen to 480 KB, time to interactive on mobile dropped from 6 to 2 seconds, and the bounce rate at the loading stage was reduced threefold.
“Conversion grew almost immediately, and the team stopped being afraid to add new blocks,” notes the client’s technical director.
Bundle analyzer: how does it differ from other analysis methods?
Bundle analyzer works not with source code but with the built artifact: it parses the module map inside chunks and shows what exactly makes the build heavier.
You can see which libraries ended up in several chunks at once, where one module is duplicated under different paths, and how much each part weighs before and after compression. This is the answer to the question “what exactly makes up what the browser downloads.”
Static analysis looks in another direction — at source code and the import graph before the build. It finds dependencies included for one function, exports that no one calls, and chains that pull in half the module tree.
In the artifact, such code is already merged with working code, so bundle analyzer will show the trace but not the cause.
Profiling is the only method that looks at runtime behavior. It shows how much time goes into parsing and executing scripts on first display, which modules run before rendering, and where long tasks that block the interface occur. These numbers are about user perception, not file weight.
The comparison of methods comes down to different questions: bundle analysis — “what is in the chunk,” static analysis — “why did it end up there,” profiling — “what of it actually hurts.” We start with profiling based on a complaint about slow loading, bundle analyzer confirms composition and volume, and static analysis carries it through to a fix in the source. No step replaces the others — the analysis proceeds as a chain, not with a single tool.
Frequent technical questions about bundle optimization
Build optimization is often postponed not because of complexity but because of doubts: will an already working product break, when should the effect be expected, and what to do with dynamic sections. Each of these questions has an engineering answer.
Will build edits break the current release?
First we capture the initial picture with the analyzer: chunk weight, dependency duplicates, the heaviest modules. Edits proceed in separate steps — from safe ones (eliminating library duplicates, moving vendors into a shared chunk) to those affecting loading order.
Each step is checked against key scenarios before rollout, and rollback means returning to the previous build.
When will the effect be visible?
Artifact size changes immediately after configuration edits — this is visible in the analyzer report on every build.
Loading speed for real visitors is affected by browser caches and varying connection quality, so we make the decision based on measurements over one to two weeks, not on a single successful run.
What to do with dynamic modules?
Dynamic import moves weight from the initial load to the moment when the visitor opens a section. There is one risk here: a shared library ends up in several chunks at once, and the total weight grows. The analyzer shows such intersections, and configuring shared groups separates them.
Should source maps be published in production?
Public access to them is an extra surface for analyzing the application from the outside. Source maps go to a private storage and are connected to the error collection system: the call stack remains readable for engineers, and only the built code is served to the visitor.
Let's discuss optimizing your bundle
The build is where engineering decisions are visible in numbers: how much the initial file weighs, what goes to the first screen, which dependencies are duplicated between parts of the application.
To speak concretely rather than by eye, we structure the first call as a review of your project together with you. Describe the task — we will return with an estimate and a work plan.
On the call we will:
- capture current build metrics: initial file weight, time to first screen, number of separate parts;
- run bundle analyzer and source-map-explorer on the current branch and review the dependency map — where duplicates are, where heavy libraries are included for one function;
- look at routes and entry points: what loads immediately and what is reasonable to defer until the user's first action;
- check the configuration: chunk splitting, reuse of shared modules, client-side caching;
- clarify constraints: legacy browser support, rollback requirements, mandatory checks before deployment;
- agree on priorities and fix a work plan with the expected effect in measurable terms.
After the call, you receive a stage-by-stage estimate and an order of actions: what gives results immediately and what requires restructuring routes. Scope and timelines depend on how much weight has accumulated and how connected the code is — we discuss this before starting.







