Small Bundles, Fast Pages: What To Do With Too Much JavaScript

In this article
Minimising the JavaScript in your pages is essential for a fast user experience.
This post will explain why bundle size matters and recommend tools and processes you can follow to monitor, visualise, and most importantly, shrink your JS bundles.
How does bundle size affect performance?#
Large amounts of JavaScript negatively affect site speed in two distinct phases:
- During page load: big bundles take longer to download.
- During parse and compile: big bundles take longer to be turned into machine code, which delays JS initialisation.
If your users happen to be on a slow, spotty network, a device with a low battery or even just an underpowered Android, large bundle size will likely cause delays during load, render, user interaction or even page scroll.
Of course, your users don’t have to be using old devices or slow networks to have a sub-par experience. The effects of a large bundle can be partially mitigated by caching, compressing and minifying script resources, though reducing the size of a bundle is the only way to guarantee a fast page.
Which performance metrics are affected by bundle size?#
All the important ones. Core Web Vitals and other key user-experience metrics are all impacted negatively by JS.
Pages with large amounts of script can delay Largest Contentful Paint, cause Cumulative Layout Shifts, and increase Interaction to Next Paint.
Slow readings in those metrics quantify poor user experiences and can result in SEO ranking penalties.
How much JavaScript is too much?#
When we’re talking about performance, we usually focus on the compressed size of resources. However, once resources are uncompressed, they will be 2–3x larger.
For example, a page with 300kb of compressed script yields 900kb–1.3mb once decompressed.

For CPU-constrained devices, multi-megabyte payloads are particularly damaging to performance. We recommend restricting pages to a maximum of < 300kb of script (compressed). Where possible, use code splitting to break up your code into small chunks of 50KB or less. That way, browsers can download JS resources in parallel, making full use of HTTP 2 multiplexing.
Alex Russell’s Performance inequality 2026, measured against a Samsung Galaxy A24 on a 9 Mbps connection, lands in the same place: 300kb of JS, 2 MB page, for a three-second load.
Tools and automations to keep your code fast#
Setup your editor for success#
Use the import cost plugin in VSCode to report the size of third party libraries as you code:

With import cost, you can set thresholds for what is considered a small or medium package. We recommend setting more aggressive targets than the defaults:
// Upper size limit, in KB, that will count a package as a small package
"importCost.smallPackageSize": 15,
// Upper size limit, in KB, that will count a package as a medium package
"importCost.mediumPackageSize": 50,Import cost can’t calculate cost-savings of two libraries with a common dependency within bundled code.
Visualise what your bundles include#
Use tools like source-map-explorer and webpack-bundle-analyzer to generate interactive bundle treemaps.
In a treemap, larger blocks correlate to larger file sizes, which makes large imports quick to spot.

Look for smaller, alternative third-party libraries#
Often we choose a library, then use it forever. There may be lighter alternatives you’re unaware of.
Using BundlePhobia.com, you can scan a project’s package.json file or search for a given npm package.

When a library is “tree shakable”, bundler tools like webpack, rollup, or esbuild can perform unused code elimination during a build. Prefer tree shakable libraries when you can.

Sometimes libraries are smaller because they don’t support older browsers. Be sure to test thoroughly for edge cases!
Block selected packages from being used#
Communicating why to use one package over another can be difficult across teams or companies. To counter this, you can use ESLint’s no-restricted-import to warn or error when a restricted package is included.
In the following example, ESLint will fail the build when we use the moment package, suggesting date-fns as a vetted alternative:
{
"rules": {
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "moment",
"message": "Use date-fns instead. See https://bundlephobia.com/package/moment"
}
]
}
]
}
}Dynamically load components and dependencies#
Most popular bundlers like Webpack, ESBuild, Rollup or Parcel can code-split your code and dependencies. Code-splitting allows you to lazy-load parts of your application as required, resulting in smaller bundle sizes and faster initial load experiences.
React, Next, Angular, and Vue all provide utilities to make lazily-loading components more straightforward. Here’s a React example:
// Lazy loading React import
const Dashboard = React.lazy(() => import("./Dashboard"));
function Page() {
return (
<Fragment>
<Suspense fallback={<Skeleton message="Loading" />}>
<Dashboard />
</Suspense>
</Fragment>
);
}Lazy-loading comes with many benefits:
- Less initial script to load
- A greater number of smaller requests loaded in parallel
- Code that isn’t changed regularly can be cached long-term
Lazy-loading works well for:
- Route/navigation based lazy-loading: load only the scripts your page requires, not the whole site.
- Interaction based lazy-loading: load dependencies as they’re required. e.g.: when a viewer opens a panel.
Prefer server-side rendering for primary content#
Whether for end-users or SEO spiders, we must render primary content as quickly as possible.
For content-driven pages, we recommend server-side rendering (SSR) over Single Page Applications (SPA). SPAs suit applications with long session times or interfaces that transition seamlessly (e.g. shopping carts), but content must still show fast.
Lazy load third party resources with facades#
Business requirements often drive the usage of third parties, but that does not mean that developers can’t influence third party performance.
At Calibre, we used a facade to take our live chat widget off the critical path, and wrote up how we did it. The library we open sourced alongside that post, react-live-chat-loader, has since been archived and is no longer maintained, so treat the pattern as the takeaway rather than the package.
A facade delays loading a third party by showing a ‘fake’ (non-interactive) chat widget, video panel, or support tool until the page has finished loading critical content.
Some strategies for third-party performance:
- Delay third parties from loading until required using facades.
- Use dns-prefetching for third party domains, e.g.:
<link rel="dns-prefetch" href="https://fonts.googleapis.com/" />. - Prefer to bundle third party libraries yourself, rather than using their CDN.
- Compare page performance with and without a given third party script. Share the results with people making decisions about third party tooling.
- Request Performance SLAs in contractual agreements with third parties.
Deliver ES6 modules to up-to-date browsers#
Supporting older browsers can hold you back from using new technologies (and their performance benefits!). Still, we need to be careful about abruptly dropping support for legacy technologies, which might result in a lack of access.
Consider splitting your build into two builds:
- An ES5 build, with browser supports, polyfills and Babel transcoding.
- An ES2015+ build, utilising
async/await, Promises, arrow functions,MapandSettypes and Dynamic Imports for lazy-loading.
<!-- Deliver ES5 code to non-module supporting browsers -->
<script nomodule src="legacy-support-bundle.js"></script>
<!-- Deliver ES2015+ code to module capable browsers -->
<script type="module" src="bundle.js"></script>For instructions to create es5 & ES2015+ builds using Webpack, see Phil Walton’s excellent post, Deploying ES2015+ Code in Production Today .
Keep monitoring JavaScript size#
Optimising bundle size doesn’t end with one housekeeping effort. As codebases grow, we need safeguards to keep JavaScript size in check.
Performance budgets set targets and create accountability for metrics. At Calibre, we set budgets for JavaScript execution metrics and third party JavaScript, and get alerts when we exceed them:

You can also use Lighthouse to set performance budgets and script your own solution.
