Writing/Cutting a landing page bundle by 10x
frontend

Cutting a landing page bundle by 10x

The order I work in when a landing page ships too much JavaScript: measure, split by route, defer everything below the fold, and keep it from growing back.

C

A landing page is the one page whose only job is to load fast for someone who has never visited before. On one React app I worked on, it was also the page shipping the most JavaScript, because it was built inside the same bundle as the logged-in product. Reducing it by 10x took no clever tricks. It took doing a few unglamorous things in the right order.

Measure what actually ships

Start with a bundle analyzer, not a guess. For a webpack build:

npx webpack-bundle-analyzer dist/stats.json

Look at three things:

  • What the landing page route pulls in. Often it's everything, because there is one entry point.
  • The largest dependencies. Charting libraries, rich-text editors, date libraries with every locale and icon packs imported whole are the usual suspects.
  • Duplicates. Two versions of the same library, pulled in by different packages.

Write the starting size down. Every later change gets measured against it.

Give the landing page its own chunk

The biggest single win is usually route-based code splitting. If the landing page and the dashboard share a chunk, every visitor downloads the dashboard.

import { lazy, Suspense } from 'react'

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

export function AppRoutes() {
  return (
    <Suspense fallback={<PageSpinner />}>
      <Routes>
        <Route path="/" element={<Landing />} />
        <Route path="/app" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  )
}

Landing is imported normally, so it's in the first chunk. Everything behind a login moves out of it.

Check what the shared layout drags in

After splitting routes, look at what the landing page still imports through shared code: a top-level provider, a layout component, a global store. In our case, state management set up for the whole app was imported at the root, along with everything it referenced. Moving providers down to the routes that need them removed a large share of what was left.

Defer what's below the fold

The landing page itself had components a visitor doesn't see on load: a testimonial carousel, an interactive demo, a pricing calculator. Those can load when they scroll into view:

const PricingCalculator = lazy(() => import('./PricingCalculator'))

function PricingSection() {
  const [ref, visible] = useInView({ rootMargin: '200px' })
  return (
    <section ref={ref}>
      {visible && (
        <Suspense fallback={<CalculatorSkeleton />}>
          <PricingCalculator />
        </Suspense>
      )}
    </section>
  )
}

Replace heavy dependencies

For each large dependency left in the landing chunk, ask whether the page needs it at all:

  • A date library for one formatted date can be replaced with Intl.DateTimeFormat.
  • An icon pack imported as a whole can be swapped for per-icon imports.
  • A utility library imported whole can become per-function imports or a few lines of code.

Keep it from growing back

A bundle shrinks once and grows forever unless something stops it. Add a size budget to CI so a pull request that grows the landing chunk past the limit fails:

{
  "path": "dist/landing.*.js",
  "limit": "120 KB"
}

Tools like size-limit or bundler performance budgets work the same way. The budget turns "someone should keep an eye on this" into a check that runs on every change.

The order matters

Splitting routes and moving providers did most of the work. Deferring below-the-fold sections and swapping dependencies finished it. The CI budget is what kept the result. If you only have an afternoon, measure first and split routes second. Those two steps usually find most of the savings.

Uzair Khan
About the author

Uzair Khan

Principal Software Engineer. Builds RAG pipelines, agent workflows, cloud platforms on AWS and GCP, and fast React frontends. Leads teams through planning, review and hiring.