JavaScript bundle size is easiest to improve when it is measured as a repeatable engineering input rather than treated as a one-time audit. This guide shows how to analyze frontend payloads, identify expensive dependencies, estimate the effect of changes, and turn bundle findings into budgets that can be checked in local builds and CI.
Overview
A frontend bundle is the JavaScript delivered for a page or application route after source files, dependencies, and assets have passed through a build system. Its size can affect download time, parsing, compilation, and execution. The impact depends on more than the file’s raw byte count: network conditions, compression, device capability, caching, and how much code is needed for the first view all matter.
For that reason, “bundle size” should be tracked using several related measurements:
- Raw size: the uncompressed output, useful for spotting unusually large modules and comparing build artifacts.
- Compressed size: the approximate transfer cost after gzip or Brotli. This is closer to network payload size, although the final response can vary by server configuration.
- Initial JavaScript: code required to render and operate the first route or view.
- Async JavaScript: code loaded later for routes, features, or interactions.
- Execution cost: the browser work required to parse, compile, and run the downloaded code.
A JavaScript bundle analyzer helps explain where bytes come from. Depending on the build system, you may use a visual report generated by a Webpack analysis plugin, a Vite-compatible analyzer, or the build output itself. The goal is not to make every chunk as small as possible. The goal is to deliver the smallest practical initial experience while keeping caching, maintainability, and application behavior intact.
Bundle work also fits into a broader performance workflow. After reducing JavaScript, review images, third-party scripts, and loading behavior. The lazy loading guide is useful when the remaining cost comes from content that does not belong in the initial view.
How to estimate
Start with a baseline for a named route and build configuration. Record the JavaScript files loaded by a production build, their raw and compressed sizes, and whether each file is part of the initial page or loaded asynchronously. Do not compare a development build with a production build: development output often includes diagnostics and different module behavior.
A simple repeatable estimate uses this sequence:
- Measure the baseline. Run the production build and record initial JavaScript, total JavaScript, and the largest individual chunks.
- Identify candidates. In the analyzer, find large packages, duplicated modules, polyfills, locale data, icon sets, and code that appears in more than one chunk.
- Choose one change. Remove an unused package, replace a broad import, split a route, or defer a feature that is not needed immediately.
- Build again. Compare the same route, output mode, and compression method. A changed chunk name or hash is not itself a size improvement.
- Check browser behavior. Use browser developer tools to inspect network transfer, caching, and main-thread activity. A smaller file can still be expensive if it causes substantial execution work.
You can express the basic change as:
estimated improvement = baseline compressed initial JavaScript - revised compressed initial JavaScript
For a fuller view, track a small table for each route:
| Measurement | Before | After | Change |
|---|---|---|---|
| Initial raw JavaScript | baseline | revised | revised minus baseline |
| Initial compressed JavaScript | baseline | revised | revised minus baseline |
| Total route JavaScript | baseline | revised | revised minus baseline |
| Largest chunk | baseline | revised | revised minus baseline |
Keep the units consistent. If one report shows decimal kilobytes and another uses binary units, convert before comparing. Also distinguish a size reduction from a transfer reduction: compression ratios can change when the contents of a chunk change.
Inputs and assumptions
A useful bundle estimate depends on clearly defined inputs. Record the following before setting a budget:
- Build tool and mode: for example, a Vite or Webpack production build with its normal minification and chunking settings.
- Target route: a landing page, authenticated dashboard, checkout flow, or another representative view.
- Measurement type: raw, gzip, Brotli, or browser-observed transfer size.
- Cache state: first visit and repeat visit answer different questions. A hashed shared chunk may be downloaded once and reused later.
- Network and device context: laboratory measurements should state the conditions used, while field data may vary considerably.
- Dependency versions: package updates can alter output even when application code has not changed.
Set separate limits for initial JavaScript and total JavaScript. A route with extensive functionality may legitimately have more deferred code, but that code should not be allowed to drift into the initial path without review. A budget is a decision signal, not a universal performance threshold. Choose a limit that reflects the application’s route complexity, audience, and delivery model, then document the reason.
When investigating a large dependency, inspect how it is imported. A package may support selective imports, but the exact result depends on its module format and the bundler’s tree-shaking behavior. Replacing a library can also create maintenance or correctness costs, so compare the measured saving with the work and risk involved. For related decisions about build systems and package managers, see the guides to JavaScript project tooling and npm, pnpm, and Yarn.
Worked examples
Example 1: a large initial dependency
Suppose a production report shows 420 kB of raw initial JavaScript and 132 kB of compressed initial JavaScript. The analyzer identifies a date and time library contributing to the main chunk, although only one formatting function is used on the first route.
The team tests a narrower import and rebuilds. The revised output is 365 kB raw and 111 kB compressed. The estimated initial transfer reduction is:
132 kB - 111 kB = 21 kB compressed
The result is worth checking in the browser, because the change may affect locale handling, formatting behavior, or other routes that share the module. If the dependency is needed later, route-level splitting may preserve functionality while keeping it out of the initial path.
Example 2: route-level code splitting
Consider an application whose home route loads an administration editor even though most visitors never open it. The baseline has 280 kB compressed initial JavaScript and 510 kB compressed after navigating through the full application.
After moving the editor behind a route boundary, the home route measures 190 kB compressed initially. The full application still reaches roughly the same total size after the editor is opened, but the first route avoids 90 kB:
280 kB - 190 kB = 90 kB deferred from the initial path
This is not a claim that every user saves exactly 90 kB. Users who open the editor may download that code later, and shared framework chunks may affect the actual transfer. The important distinction is between reducing total functionality and changing when functionality is delivered.
Example 3: setting a budget
Assume a route currently has 155 kB of compressed initial JavaScript. The team chooses a provisional budget of 170 kB to allow normal feature work while leaving a review margin. A pull request that produces 168 kB passes, but a later change producing 181 kB should trigger investigation. The budget does not automatically reject the feature; it prompts the team to explain the increase, split code, remove an unused import, or revise the budget deliberately.
When to recalculate
Recalculate bundle size whenever a dependency, build configuration, route boundary, or major feature changes. Package upgrades deserve a fresh production build because output can change through different module entry points, retained code, or updated transitive dependencies. Recheck after changing minification, target browser settings, compression, asset hosting, or caching rules as well.
Make the review part of the normal delivery process:
- Run the production build in CI using the same lockfile and build command used for deployment.
- Compare route-level initial and total JavaScript against the stored baseline.
- Report the largest increases and identify which dependency or chunk caused them.
- Require a short explanation for budget exceptions rather than silently accepting drift.
- Periodically inspect real browser performance and deployment behavior, not only build reports.
Revisit the budget when the product’s route structure or audience changes, not simply because a number is inconvenient. Keep a dated record of the build mode, compression method, and representative routes so future comparisons remain meaningful. For a broader release review, combine this process with the web deployment checklist. A small, documented bundle workflow is easier to repeat than a large audit performed only after performance has already declined.