Reduce JavaScript Bundle Size: Measure, Split and Remove Code

Crawvix Editorial Team · Published October 29, 2026 · Last updated October 29, 2026 · 10 min read

Every kilobyte of JavaScript costs more than a kilobyte of image. The browser has to download it, then parse it, compile it and run it, and that work happens on the main thread that also handles taps and scrolling. A page that ships one large bundle on every route makes every visitor pay for features they may never use.

This guide shows you how to reduce JavaScript bundle size in a way you can measure. You will find out what is inside your bundle, remove what is unused, split what remains so each page loads only its own code, and check that nothing broke. It is for site owners and developers who use a bundler such as webpack, Rollup or Parcel, or a framework built on one. If you have no build step, many of the same ideas still apply to the scripts you load by hand.

At the end you will have a before-and-after size report, a short list of dependencies you replaced or removed, and a habit for keeping the bundle from growing back.

Before you begin

You need the source code of the site, the ability to run a production build, and a way to deploy to a test environment first. Work on a branch. Record the baseline before you change anything: the size of each JavaScript file the home page and one or two other key templates load, both raw and compressed, and the Lighthouse results for the same pages on a mobile profile.

Plan to test interactive features after each change, since removing the wrong code leads to broken buttons rather than a build error. Allow a day for a first pass on a typical project.

Do not expect a fixed saving. How much you gain depends on your dependencies and architecture, and web.dev's tree shaking article says the benefit may be small for some apps. Its own example shrank one bundle from 20.8 KiB to 8.46 KiB, which shows the type of change possible, not a result you should assume.

Why bundle size matters

web.dev explains that shipping all of a site's JavaScript at startup adds parse, compile and execution work on the main thread. That delays interactivity and can worsen Interaction to Next Paint (INP). On client-rendered sites a heavy bundle can also delay Largest Contentful Paint (LCP), for example when the browser discovers the main content late or is too busy to paint it. See our guides on Interaction to Next Paint and Largest Contentful Paint for how to measure those two.

The Chrome documentation on network payloads adds a point about cost. Large payloads correlate with long load times, and they can cost visitors money on metered connections. Lighthouse's "Avoid enormous network payloads" audit flags pages whose total request size is above 5,000 KiB, and it suggests aiming for roughly 1,600 KiB in total. That figure covers everything the page loads, not only JavaScript, and it is a target from the documentation, not a rule from a search engine.

Step-by-step instructions

Step 1: Measure what you ship

You cannot trim what you cannot see. Start with a production build, then look inside it.

  • In Chrome DevTools, open the Network panel, filter by JS and reload. Note each file's transferred size and uncompressed size.
  • Open the Coverage tool (Command Menu, then "Show Coverage"), reload, and see how much of each script ran on the page. Lighthouse's "Reduce unused JavaScript" audit uses the same idea and, according to the Chrome documentation, flags every script file with more than 20 KiB of unused code.
  • Run a bundle analyzer. web.dev names Webpack Bundle Analyzer and source-map-explorer for finding bloat in larger projects. Both produce a map of which packages take up the space.

Write down the top five items by size. A single dependency is often a large part of the total.

What success looks like: You have a table of the largest scripts and packages for your main templates, with a baseline total.

Step 2: Remove what you do not need

Start with the simple wins.

  1. Delete dead dependencies. Search for packages in package.json that nothing imports any more, and remove them.
  2. Replace heavy libraries for small jobs. If you import a large date, utility or animation library for one function, check whether a native browser feature or a small module covers it. Test the replacement carefully.
  3. Remove duplicated copies. The analyzer often shows the same package twice at different versions. Align the versions so the bundler includes it once.
  4. Review third-party tags. Analytics, chat widgets and A/B tools are separate scripts that sit outside your bundle. Our guide on third-party scripts and performance covers how to weigh them.
  5. Confirm minification. The Chrome documentation says the Minify JavaScript audit lists unminified files and potential savings, and names Terser as a popular tool. In a framework, make sure you deploy the production build, which is minified. For React, the documentation says to deploy the production build.

What success looks like: The bundle no longer contains packages you did not intend to ship, and every file is minified.

Step 3: Enable tree shaking

Tree shaking removes code that you import but never use. web.dev describes it as dead code elimination that depends on ES modules and static import statements. Importing a named export lets the bundler drop the rest of the module:

// Pulls in only what is used, so the bundler can prune the rest.
import { simpleSort } from "./utils/utils";

Check three things:

  • Do not let your compiler convert modules too early. web.dev says that with @babel/preset-env you should set modules: false; otherwise Babel may output CommonJS, which makes shaking harder.
  • Declare side effects. For webpack, setting "sideEffects": false in package.json tells the bundler no module in the project has side effects, so unused modules can be dropped. You can instead list only the files that do have side effects, such as a stylesheet import or a polyfill. Files you do not list are treated as side-effect free, so list carefully.
  • Test a production build. web.dev notes development builds do not get the benefit, because the whole module is imported regardless.

What success looks like: After a production build, the analyzer shows that modules you import only partly are smaller than before.

Step 4: Split code by route and by feature

Even needed code does not have to load on the first page. web.dev describes three approaches.

  • Route or component splitting. Send only the code that the initial route needs and load the rest later. With a client-side framework this is often the simplest option. For React without server rendering, the Chrome documentation points to React.lazy(); for Vue with the router, lazy loading routes.
  • Dynamic import(). Load a module only when it is needed, for example when a form is submitted or a panel opens.
  • Vendor splitting. Put third-party packages into a separate bundle, which can be cached because it changes less often than your own code. web.dev points to webpack's SplitChunksPlugin for this.

A dynamic import looks like this:

const button = document.querySelector('#export-report');

button.addEventListener('click', async () => {
  // The chart library is downloaded only when someone asks for the report.
  const { renderReport } = await import('./report.js');
  renderReport();
});

Be careful not to defer something the first screen needs. Splitting a component that appears above the fold, and loading it late, can make LCP worse. web.dev also mentions that preloading important chunks can fetch them sooner. If your site relies entirely on client-side rendering, web.dev notes that server-side rendering helps make sure the server sends meaningful markup.

What success looks like: The home page loads a smaller initial script, and heavy features appear as separate files requested when used.

Step 5: Load what remains in the right way

Once the bundle is smaller, tell the browser how to treat it. Where a script is not needed for the first paint, load it with defer or async so it does not block parsing. Check that scripts which must run early, such as consent handling, still run in the right order. Our guide on render-blocking CSS and JavaScript explains safe ordering.

Also turn on text compression for JavaScript on the server, which shrinks transfer size without changing code. Brotli and gzip are covered in our guide to compression. The Chrome documentation lists minification and text compression together as ways to cut payload size.

What success looks like: The Network panel shows compressed transfer sizes well below the uncompressed sizes, and no script blocks the first render without a reason.

Step 6: Check, then prevent regression

Rebuild, then repeat the Step 1 measurements on the same pages. Compare raw size, transferred size, unused code and Lighthouse results, using several runs because single results vary.

Then click through the site: navigation menus, forms, search, checkout in test mode, consent banner. Check the console for new errors; see finding JavaScript console errors.

To stop the bundle growing back, set a size limit in your build or continuous integration. Our guide on Lighthouse CI and performance budgets shows how to fail a build when a file grows past a limit you chose.

What success looks like: You have a documented before and after, the site still works, and an automated check will flag growth.

Final verification checklist

  • You recorded baseline sizes (raw and transferred) for the main templates.
  • A bundle analyzer report identifies the largest packages.
  • Unused dependencies and duplicate versions are removed.
  • The deployed bundle is a minified production build.
  • Tree shaking works: named imports are pruned and sideEffects is set correctly.
  • Heavy features load through dynamic imports or route splitting.
  • Nothing needed for the first screen is deferred in a way that delays it.
  • JavaScript responses are compressed.
  • Forms, menus, search and checkout were tested after the change.
  • A budget or CI check will warn you if bundles grow.

Common mistakes to avoid

  • Optimizing without measuring. You may spend a day on a package that is 2 percent of the bundle. Use the analyzer first.
  • Marking sideEffects as false when files do have side effects. The bundler may drop a polyfill or style import that you needed. List files that must stay.
  • Testing a development build. web.dev says tree shaking does not help there. Judge your results on production output.
  • Splitting too much. Dozens of tiny files can cost more in requests than they save. Split along routes and large features.
  • Lazy-loading above-the-fold code. If the first screen depends on it, loading it late delays what people see. Keep first-screen code in the initial load.
  • Ignoring third-party scripts. They do not appear in your bundle analyzer but add to download and run time. Review them separately.
  • Removing code without regression tests. Clean-ups can break click handlers. Test the pages people use most.

Frequently asked questions

What is a good JavaScript bundle size?

There is no official number for JavaScript alone. The Chrome documentation suggests a total page payload under about 1,600 KiB and flags pages over 5,000 KiB, but your audience, devices and features matter. Set a budget from your own measurements.

Does a smaller bundle improve my ranking?

It can make pages respond faster, but nobody can promise a ranking change from it. Treat it as a user experience improvement that supports your Core Web Vitals work.

Is code splitting safe for SEO?

Splitting is about when your code loads. A crawler does not click or type on your page, so if main content appears only after a user action triggers a script, it may not be seen. Content that is in the page when it loads is the safe default. Check the rendered HTML with the URL Inspection tool in Search Console.

Do I need server-side rendering?

Not always. web.dev notes that for a site relying entirely on client-side rendering, server-side rendering helps ensure meaningful markup from the server. Whether that is worth it depends on how your content is delivered.

Is a bundle analyzer enough?

It shows what is in your bundles, not how long it takes to run. Combine it with DevTools coverage and a Lighthouse trace to find code that is downloaded but never used, and code that runs for a long time.

Official references


Next step: See how Crawvix checks a website and capture a baseline once scanning opens. This guide provides technical information, not a guarantee of rankings, traffic or index inclusion.

In this category

All 25 Performance guides