Reference guide · html-css-js · Published 2026-08-15 · 4 min read

Reducing JavaScript bundle size

Reduce JavaScript bundle size: measure, code split, tree shake, remove dead code, ship production builds and inspect source maps.

Measure first, cut second

Every byte of JavaScript must be downloaded, parsed, compiled and executed, and unlike CSS most of that work happens on the main thread. The heavy JavaScript slow site article shows the effect in the Interaction to Next Paint metric. Before touching anything, measure so the effort lands where the size lives.

The way to see the real number: open the built site in an incognito window, put the Network panel on the JS filter, hard reload, and read the transferred size per script. Then open the coverage tab in the DevTools, reload, and record the unused ratio for each file. Most apps discover that one utility module is shipped everywhere.

Cutting the bundle

Code splitting

Code splitting turns one giant chunk into several chunks that load on demand. The route for a page that uses a chart library is a textbook case: the chart only matters on that route, so the library enters its own chunk loaded when the route renders, not at first paint.

const Chart = () => import("./chart.js");

Tree shaking

Tree shaking removes exports that are defined but never imported. It only works when the build is in production mode and the module is written with static import and export (not module.exports). A library imported as import { debounce } from "lodash" lets the bundler drop the rest. Imported as import * as _ from "lodash", the whole library enters the bundle, because tree shaking cannot see which parts are unused.

Remove dead and duplicate code

Production builds and source maps

Development builds include assertions, hot reload and verbose error messages, easy to ship by accident. Set the build to production, then confirm minification and tree shaking across the whole dependency graph. Keep source maps in the build output but serve them only with the authenticated source map server, or strip them from prod entirely. The 20 to 50 percent saving is not the effect of stripping maps in a normal build; it is the gap of a dev-mode bundle actually shipped, because dev builds carry assertions, hot reload and source maps the visitor never needs. The browser's console footer warning about missing maps exists to catch exactly that mistake.

The bundle-size table

MoveTypical savingWarning
Production build, not dev20 to 50 percentSaving is dropping the dev build, not maps alone
Tree shaking10 to 60 percent, depends on importsES modules only
Code splitting on routesWorst route shrinks hardChunk duplication if shared libraries split
Removing a dead libraryExact size of the fileLong tail of references first
Replacing full importSize of the unused shareSource compatibility

Verification

Rerun the total transfer size with the same repeat measurement, then the Interaction to Next paint number from the lab, then watch the page in the network panel while switching routes to prove the on-demand chunks load. A bundle weight that went down but a TTFB that went up is a chunk boundary in the wrong place, see the core web vitals article for the wholesale view.

Prevention

Bundles creep. Set the CI alert when the transfer size exceeds a threshold, add the duplicate-dependency check, and write a one-line story for every new dependency: what it imports, and how much tree shaking cuts it. When the pain returns, the render blocking resources pass and the slow page article are the next two places to look.

Need a website built, fixed, optimised, migrated or replaced?

This technical resource is written by CSMBAC, a small design and development studio. If you would rather hand the problem to a professional, the website service page explains how we build enquiry-ready websites.

Explore website services