← Back to the library
FrontendApplied · 8 min

The first page load is slow because of a huge JavaScript bundle

“Mobile users stare at a blank or skeleton screen for seconds, Lighthouse flags "Reduce unused JavaScript", and the main chunk has grown to megabytes without anyone deciding it should.”

THE PRINCIPLE

Bytes are not the only cost: every kilobyte of JavaScript is also parse and execute time on the slowest phone you support. LCP is good at 2.5 s or less and poor above 4 s.

FIRST MOVES

  1. Open the production bundle in a treemap analyzer (rollup-plugin-visualizer, webpack-bundle-analyzer, source-map-explorer) and list the five biggest modules and why each is in the entry chunk.
  2. Break LCP into TTFB, resource load delay, load duration, and render delay; client rendering usually shows up as large load and render delays.
  3. Split by route and by rarely used heavy widgets (editors, charts, maps) with dynamic import() and React.lazy declared at module top level.
  4. Check that tree shaking works: ES module imports, no Babel transform to CommonJS, accurate "sideEffects" in package.json, and no barrel files pulling in a whole library.
  5. Audit third-party tags: defer or async non-critical scripts, load them after interaction or idle, and remove ones nobody owns.

TOOLS: THEN → NOW

One vendor bundle built for everythingRoute-level splitting from the framework plus on-demand import() for heavy widgets
Fully client-rendered shellSSR, static prerendering, or islands so the LCP element is in the HTML
Bundle size noticed in productionSize budgets in CI (size-limit, bundler warnings) and the Lighthouse treemap

PATTERN SNAPSHOT

import { lazy, Suspense } from "react";

// Declared at module scope: code loads the first time it renders.
const ChartPanel = lazy(() => import("./ChartPanel"));

export function Report({ showChart }: { showChart: boolean }) {
  return (
    <>
      <Summary />
      {showChart && (
        <Suspense fallback={<ChartSkeleton />}><ChartPanel /></Suspense>
      )}
    </>
  );
}

CLOSE THE AI. EXPLAIN THIS.

You cut the bundle by 40% but LCP barely moved. Which LCP sub-part would you look at next, and why might the LCP image still wait on JavaScript?

HOW IT WORKS UNDERNEATH

SOURCES

Guide reviewed