Skip to main content

Bundle Size Optimization: Tools and Techniques for 2026

·PkgPulse Team

Every kilobyte of JavaScript you ship costs your users time and your business money. A 100KB bundle takes 1-2 seconds to parse on a mid-range phone. For e-commerce, each extra second of load time reduces conversions by 7%.

Here's how to measure, analyze, and shrink your JavaScript bundles in 2026.

Why Bundle Size Matters

The Performance Tax

Bundle SizeParse Time (Mobile)Parse Time (Desktop)
100 KB200-300ms50-100ms
500 KB1-1.5s200-400ms
1 MB2-3s400-800ms
2 MB4-6s800ms-1.5s

This is just parse time — before the code even runs. Add execution time, and large bundles create a noticeably sluggish experience.

The SEO Impact

Google's Core Web Vitals directly factor in JavaScript performance:

  • Largest Contentful Paint (LCP) — Heavy bundles delay content rendering
  • Interaction to Next Paint (INP) — Large bundles block the main thread
  • Time to First Byte (TTFB) — Larger transfers take longer

Step 1: Measure What You Have

Before optimizing, measure. You can't improve what you don't understand.

Bundle Analysis Tools

webpack-bundle-analyzer

The classic. Generates an interactive treemap of your bundle contents.

npm install --save-dev webpack-bundle-analyzer

# Add to webpack config
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
  plugins: [new BundleAnalyzerPlugin()]
};

source-map-explorer

Lighter weight than webpack-bundle-analyzer. Works with any build tool that generates source maps.

npx source-map-explorer dist/bundle.js

@next/bundle-analyzer

Purpose-built for Next.js projects:

npm install @next/bundle-analyzer

# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({ /* your config */ });

# Run
ANALYZE=true npm run build

bundlephobia.com

Check the size of any npm package before you install it. Shows minified size, gzipped size, download time, and composition.

Check package sizes on PkgPulse too — we show bundle size alongside health scores and download trends.

Step 2: Tree-Shaking

Tree-shaking removes unused code from your bundle. Modern bundlers (Vite, webpack 5, Rspack) do this automatically for ES modules.

Make Sure It Works

Tree-shaking only works with ES module syntax (import/export). CommonJS (require) can't be tree-shaken.

// ✅ Tree-shakeable — only 'debounce' is included in the bundle
import { debounce } from 'lodash-es';

// ❌ NOT tree-shakeable — entire lodash library is included
const { debounce } = require('lodash');

Choose Tree-Shakeable Packages

When comparing packages on PkgPulse, prefer those that ship ES modules. Look for "module" or "exports" fields in package.json.

Common swaps for smaller bundles:

Heavy PackageLighter AlternativeSize Reduction
lodash (72KB)lodash-es (tree-shakeable)60-90%
moment (72KB)date-fns (tree-shakeable)80-95%
axios (13KB)ky (3KB) or native fetch75-100%
uuid (3.5KB)crypto.randomUUID() (0KB)100%

Step 3: Code Splitting

Don't load everything upfront. Split your bundle into chunks that load on demand.

Route-Based Splitting (React)

import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

Component-Based Splitting

Lazy-load heavy components that aren't immediately visible:

// Only load the chart library when the user scrolls to the chart section
const Chart = lazy(() => import('./components/Chart'));

function Analytics() {
  const [showChart, setShowChart] = useState(false);

  return (
    <div>
      <h1>Analytics</h1>
      <button onClick={() => setShowChart(true)}>Show Chart</button>
      {showChart && (
        <Suspense fallback={<ChartSkeleton />}>
          <Chart />
        </Suspense>
      )}
    </div>
  );
}

Dynamic Imports for Heavy Libraries

// Don't import marked at the top level
// import { marked } from 'marked'; // ❌ 40KB added to main bundle

// Load it when needed
async function renderMarkdown(text) {
  const { marked } = await import('marked'); // ✅ Loaded on demand
  return marked(text);
}

Step 4: Replace Heavy Dependencies

The biggest wins often come from swapping out bloated packages.

Date Libraries

LibrarySize (min+gzip)Tree-Shakeable
Moment.js72KB❌ (deprecated)
date-fns2-10KB (per function)
Day.js2.9KBPartial
Temporal (native)0KBN/A (built-in)

UI Component Libraries

LibraryFull SizeWith Tree-Shaking
Ant Design340KB~80-150KB
Material UI300KB~60-120KB
Radix + Tailwind~20-40KB~15-30KB
shadcn/ui~15-30KB~10-25KB

Compare component libraries on PkgPulse to make informed choices.

Step 5: Compression

Gzip vs Brotli

CompressionTypical RatioBrowser Support
None1xAll
Gzip3-5x99%+
Brotli4-6x97%+

Brotli compresses 15-20% better than gzip. Most CDNs and hosting providers support it. Enable it:

// Next.js (next.config.js)
module.exports = {
  compress: true, // Gzip by default
};

// Vite (vite.config.js)
import viteCompression from 'vite-plugin-compression';
export default {
  plugins: [
    viteCompression({ algorithm: 'brotliCompress' }),
  ],
};

Step 6: Set a Budget

Define a bundle size budget and enforce it in CI:

// bundlesize config in package.json
{
  "bundlesize": [
    {
      "path": "./dist/main.*.js",
      "maxSize": "150 kB"
    },
    {
      "path": "./dist/vendor.*.js",
      "maxSize": "250 kB"
    }
  ]
}

Or use Lighthouse CI with performance budgets:

{
  "ci": {
    "assert": {
      "assertions": {
        "resource-summary:script:size": ["error", { "maxNumericValue": 300000 }]
      }
    }
  }
}

Optimization Checklist

  1. ✅ Analyze your bundle with a visualization tool
  2. ✅ Ensure tree-shaking works (ES modules, sideEffects field)
  3. ✅ Code-split routes and heavy components
  4. ✅ Replace heavy dependencies with lighter alternatives
  5. ✅ Enable Brotli compression
  6. ✅ Set and enforce a bundle size budget
  7. ✅ Lazy-load below-the-fold content
  8. ✅ Use dynamic imports for rarely-used features
  9. ✅ Remove unused CSS (PurgeCSS or Tailwind's built-in purge)
  10. ✅ Optimize images (next/image, sharp, AVIF format)

Conclusion

Bundle size optimization isn't a one-time task — it's an ongoing discipline. Start by measuring, then make targeted improvements where the data shows the biggest impact. The tools and techniques in 2026 make it easier than ever to ship fast, lean JavaScript.

Use PkgPulse to compare package sizes before adding new dependencies — it's the easiest way to prevent bundle bloat before it starts.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.