JavaScript bundle size and code splitting

Large bundles delay loading and block taps. How to see what is in yours, remove what is not needed, and split the rest so each page loads only its own code.

AdvancedHow-to6 min readUpdated October 30, 2026

In this guide
  1. Why bundle size matters
  2. Step 1: see what is in the bundle
  3. Step 2: remove what you do not need
  4. Step 3: stop sending old-browser code to new browsers
  5. Step 4: split the bundle
  6. Step 5: cache and compress
  7. Step 6: keep it from growing back
  8. If you cannot change the build
  9. Check the result
  10. Check your own page
  11. Sources

Why bundle size matters

web.dev puts the cost plainly: even unused JavaScript has to be downloaded, parsed, compiled and then executed. That adds delay in loading, in memory and in main-thread activity, and the main thread is the one that must answer taps and typing. Shipping less code at startup helps the page become interactive sooner. It can help Largest Contentful Paint too, when the main content is drawn by JavaScript, because a smaller bundle arrives and runs sooner. See where main-thread time goes.

How big is the problem? In the 2025 Web Almanac the median mobile home page loaded 632 KB of JavaScript. Those are bytes over the wire, after compression. The same chapter measured about 251 KB of unused JavaScript on the median mobile page, counted after the files were unpacked, and it notes that unpacked sizes can be several times larger than the transferred ones. The typical single file is small, around 5 KB, so the weight comes from how many files there are and from a few large ones.

Step 1: see what is in the bundle

You cannot cut what you cannot see.

Step 2: remove what you do not need

Step 3: stop sending old-browser code to new browsers

To support older browsers, build tools add polyfills and rewrite newer syntax into older syntax. That makes files larger. The Legacy JavaScript insight in Lighthouse 13.5 reports scripts where such code would save 5,000 bytes or more if removed. Its advice is to change the build so that it does not transpile features that all current browsers support, unless you know that you must support older browsers.

The practical steps are:

  1. Find out which browsers your visitors really use, from your analytics.
  2. Set the build tool's target list to match. web.dev's guide uses a preset that transpiles only what the chosen browsers need.
  3. Rebuild and compare the file sizes.

Older guidance suggested serving two builds, one with type="module" for modern browsers and a fallback for the rest. A single modern build is simpler if the browsers you must support are recent.

Step 4: split the bundle

Code splitting means sending several files instead of one and loading each when needed. MDN names two kinds: splitting by entry point, which separates the code for each page or route, and dynamic splitting, which separates code that is loaded with a dynamic import().

A dynamic import is a function-like expression that loads a module on demand and returns a promise. web.dev's example loads a library only after a form is submitted:

form.addEventListener('submit', async (e) => {
  e.preventDefault();
  const { default: moduleA } = await import('library');
  moduleA.doTheWork();
});

The code of the module is no longer part of the first bundle. It is fetched when it is first needed.

What to split off:

Two cautions. First, a chunk that is only discovered after another script has run adds a step to the chain of requests. For code the page will certainly need, add a modulepreload or preload hint so it is fetched early. See resource hints. Second, do not split into hundreds of tiny files. With HTTP/2 and HTTP/3 several files are not a problem, as HTTP/2 and HTTP/3 explains, but each file still has a cost.

web.dev also advises keeping third-party packages in a separate vendor chunk. It changes less often than your own code, so visitors can keep it in their cache after you release an update.

Step 5: cache and compress

Give the files names that change with their content, serve them with a one-year lifetime and immutable, and compress them. See HTTP caching headers and text compression. A visitor who has already downloaded a chunk does not download it again until it changes.

Step 6: keep it from growing back

If you cannot change the build

On a hosted builder or a theme-based CMS you rarely control bundling. The same ideas apply through different levers: remove plugins and apps that load scripts on every page, load page-specific scripts only on their pages, and delay what the first screen does not need. See defer, async and module scripts.

Check the result

  1. Compare the size of the JavaScript requests before and after, in compressed and unpacked terms.
  2. Run Lighthouse and check Reduce unused JavaScript, JavaScript execution time and Total Blocking Time.
  3. Open the Coverage tool again and compare the unused share.
  4. Watch Interaction to Next Paint in the field. See the INP guide.

Check your own page

Run the free audit and look at the largest JavaScript files, Reduce unused JavaScript, and the Legacy JavaScript and Duplicated JavaScript insights. A large file that is mostly unused on the home page is the best place to start.

Sources

Start here

Free website audit

Live Google data for any public page: mobile and desktop.

Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it

Rather have this done for you? Speed optimization · Book a call

Related guides

All guides →