Lazy-load behind Suspense

Keep heavy, non-critical components out of the first bundle.

From frontend-patterns, section “Code Splitting & Lazy Loading”.

What loads before first click

Pick a network, press Load, compare.

Static importBad
Usable –Chart –JS 480 KB
React.lazy + SuspenseGood
Usable –Chart –JS 60 KB
import { lazy, Suspense } from 'react'

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

export function Dashboard({ data }) {
  return (
    <Suspense fallback={<ChartSkeleton />}>
      <HeavyChart data={data} />
    </Suspense>
  )
}

Fallback size decides the jump

Press Load chart and watch the Continue button.

fallback={null}Bad
CLS –
<Spinner /> 40pxBad
CLS –
<ChartSkeleton />Good
CLS –
<Suspense fallback={<ChartSkeleton />}>
  <HeavyChart data={data} />
</Suspense>

function ChartSkeleton() {
  return <div className="h-[180px] rounded-lg bg-muted" aria-hidden />
}

Decoration loads quietly

Press Reload and notice where your eye goes.

fallback={<Spinner />}Bad
Spinner 0 s
fallback={null}Good
Spinner 0 s
const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))

<Suspense fallback={null}>
  <ThreeJsBackground />
</Suspense>

Why it matters, pros and cons

Why it matters

The first bundle runs before the page responds. Splitting defers a chart's cost.

Pros

  • Less JS before the page is usable.
  • Same-size skeleton keeps CLS at 0.
  • fallback={null} keeps decoration quiet.

Cons

  • Slows anything needed at first paint.
  • Small components aren't worth a round trip.
  • A wrong-size skeleton still shifts the page.