Production Engineering/
Lesson

Your JavaScript bundleWhat is bundle?A single JavaScript file (or set of files) that a build tool creates by combining all your source code and its imports together. is downloaded, parsed, and executed before your app becomes interactive. On a fast connection this might take 200ms. On a mid-range phone on a 4G connection, that same bundle might take 3 seconds. Bundle optimization is about making sure users only download and execute the code they actually need for the page they're on.

Understanding what's in your bundleWhat is bundle?A single JavaScript file (or set of files) that a build tool creates by combining all your source code and its imports together.

Bundle analysis

Before you can optimize, you need to see what you're working with. Bundle analyzers visualize every package and file in your output, sized by contribution.

# For Vite projects - install the visualizer plugin
npm install --save-dev rollup-plugin-visualizer

# For webpack projects
npm install --save-dev webpack-bundle-analyzer
// vite.config.js - add the visualizer plugin
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    visualizer({
      open: true,        // opens the report in your browser after build
      gzipSize: true,    // shows gzip-compressed size
      brotliSize: true,  // shows brotli-compressed size
    })
  ]
});

After running npm run build, you'll see a treemap of every file in your bundle. Common surprises: moment.js (230KB), lodash (70KB when you only use 3 functions), and icon libraries where you imported the whole set but only use 10 icons.

Run npx bundlephobia <package-name> to check the size of any npm package before you install it. It also shows you alternatives.
02

Code splittingWhat is code splitting?Breaking your application into smaller JavaScript chunks that load on demand so users only download the code needed for the page they're viewing.

Route-based splitting

Code splitting is the practice of breaking your bundleWhat is bundle?A single JavaScript file (or set of files) that a build tool creates by combining all your source code and its imports together. into multiple chunks, where each chunk is loaded only when needed. The most natural boundary is routes, users visiting your homepage don't need the code for your settings page.

// Without code splitting - everything loads upfront
import { SettingsPage } from './pages/Settings';
import { DashboardPage } from './pages/Dashboard';
import { ProfilePage } from './pages/Profile';

// With React lazy + code splitting - each page loads on demand
import { lazy, Suspense } from 'react';

const SettingsPage = lazy(() => import('./pages/Settings'));
const DashboardPage = lazy(() => import('./pages/Dashboard'));
const ProfilePage = lazy(() => import('./pages/Profile'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/settings" element={<SettingsPage />} />
        <Route path="/dashboard" element={<DashboardPage />} />
        <Route path="/profile" element={<ProfilePage />} />
      </Routes>
    </Suspense>
  );
}

Component-level splitting

You can split at the component level too, not just routes. Large components that aren't immediately visible, modals, drawers, complex charts, are good candidates.

import { lazy, Suspense, useState } from 'react';

// The chart library is large (200KB). Only load it when the user opens the modal.
const AnalyticsChart = lazy(() => import('./components/AnalyticsChart'));

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

  return (
    <div>
      <button onClick={() => setShowChart(true)}>View analytics</button>
      {showChart && (
        <Suspense fallback={<div>Loading chart...</div>}>
          <AnalyticsChart />
        </Suspense>
      )}
    </div>
  );
}
03

Tree shakingWhat is tree shaking?A build tool feature that removes unused exported code from your final bundle, reducing the amount of JavaScript shipped to users.

How it works

Tree shaking is the process of removing code that is exported but never imported anywhere in your application. Modern bundlers (Vite, webpack 5, Rollup) do this automatically for ES modulesWhat is es modules?The official JavaScript module standard using import and export - enabled in Node.js via "type": "module" in package.json..

// utils.js - exports 5 functions
export function formatDate(date) { /* ... */ }
export function formatCurrency(amount) { /* ... */ }
export function slugify(text) { /* ... */ }
export function capitalize(text) { /* ... */ }
export function truncate(text, length) { /* ... */ }

// app.js - only imports 2
import { formatDate, formatCurrency } from './utils';

// After tree shaking: slugify, capitalize, and truncate are NOT in the bundle

Tree shaking only works with ES module syntax (import/export). CommonJSWhat is commonjs?Node.js's original module format using require() and module.exports - distinct from and predating the ES module import/export syntax. (require/module.exports) cannot be tree-shaken reliably because require is dynamic.

Avoiding accidental barrel fileWhat is barrel file?An index.js that re-exports from multiple modules in one place so consumers can import everything from a single path - can hurt tree shaking if overused. bloat

Barrel files (index.js that re-exports everything from a directory) can break tree shaking if bundlers can't determine which exports are safe to drop.

// components/index.js (barrel file) - can hurt tree shaking
export { Button } from './Button';
export { Modal } from './Modal';       // Modal imports a large dependency
export { DataTable } from './DataTable'; // DataTable imports another large dep

// If you do this in your app:
import { Button } from './components';
// Some bundlers may include Modal and DataTable anyway

// Better: import directly from the source file
import { Button } from './components/Button';
You can check if your bundler is tree-shaking correctly by searching for the name of a function you know you didn't use in the minified output. If it's there, tree shaking failed for that module.
04

DependencyWhat is dependency?A piece of code written by someone else that your project needs to work. Think of it as a building block you import instead of writing yourself. optimization

Replace heavy libraries

Many popular libraries have lightweight alternatives or can be replaced with native browser APIs that didn't exist when the library was created.

// Instead of moment.js (230KB gzipped: 72KB)
import moment from 'moment';
const formatted = moment(date).format('MMMM D, YYYY');

// Use date-fns (tree-shakable, only pay for what you use)
import { format } from 'date-fns';
const formatted = format(date, 'MMMM d, yyyy');

// Or use the native Intl API (zero bundle cost)
const formatted = new Intl.DateTimeFormat('en-US', {
  year: 'numeric', month: 'long', day: 'numeric'
}).format(date);
// Instead of importing all of lodash (70KB)
import _ from 'lodash';
const result = _.groupBy(items, 'category');

// Import only the function you need (2KB)
import groupBy from 'lodash/groupBy';
const result = groupBy(items, 'category');

// Or use native Array methods (zero cost)
const result = items.reduce((acc, item) => {
  (acc[item.category] = acc[item.category] || []).push(item);
  return acc;
}, {});
LibrarySize (gzipped)AlternativeSize
moment.js72KBdate-fns (tree-shaken)~3KB
lodash24KBlodash/method + native1–5KB
axios12KBnative fetch0KB
jQuery30KBnative DOM APIs0KB
Font Awesome (all icons)50KB+lucide-react (tree-shaken)~2KB
05

Core Web VitalsWhat is core web vitals?Three Google-defined metrics (loading speed, interactivity, visual stability) that measure real-user experience and affect search rankings. and bundleWhat is bundle?A single JavaScript file (or set of files) that a build tool creates by combining all your source code and its imports together. performance

The metrics that matter

Google's Core Web Vitals give you concrete, measurable goals to work toward. Two of the three are directly affected by your JavaScript bundle size.

LCP (Largest Contentful Paint) - target: < 2.5 seconds
  The time until the largest visible element is rendered.
  Large render-blocking scripts delay this.

INP (Interaction to Next Paint) - target: < 200ms
  How quickly the page responds to user interactions.
  Long JavaScript tasks block the main thread and hurt this.

CLS (Cumulative Layout Shift) - target: < 0.1
  How much the layout shifts unexpectedly.
  Less related to JS size, more to image dimensions and font loading.
<!-- Defer non-critical scripts to avoid blocking rendering -->
<script src="/analytics.js" defer></script>

<!-- Async scripts don't block HTML parsing -->
<script src="/chat-widget.js" async></script>

<!-- Preload critical resources the browser discovers late -->
<link rel="preload" href="/fonts/jetbrains-mono.woff2" as="font" crossorigin>
Use <link rel="modulepreload"> instead of <link rel="preload"> for ES module chunks. It not only downloads the file but also parses and compiles it, giving you a meaningful head start.
06

Quick reference

TechniqueMechanismTypical impact
Route-based code splittingReact.lazy + dynamic import()30–60% smaller initial bundle
Tree shakingES module import/exportRemoves dead code automatically
Replace moment.jsdate-fns or Intl APISave 70KB gzipped
Import lodash functionsimport fn from 'lodash/fn'Save 20KB gzipped
Lazy load heavy componentsDynamic import() + SuspenseMoves cost to when it's needed
defer non-critical scripts<script defer>Unblocks HTML rendering
Bundle analysisrollup-plugin-visualizerIdentifies what to optimize first