stackademic

The leading education platform for anyone with an interest in software development.

Next.js Streaming with Suspense

Stream UI progressively using React Suspense boundaries and loading files

Overview

Streaming lets the server send HTML in chunks so users see content before all data is ready. In the App Router you enable it with React <Suspense> boundaries or a loading.tsx file. Slow data fetches render a fallback first, then swap in the real content as it resolves, improving perceived performance.

Syntax / Usage

Wrap a slow async component in <Suspense> and provide a fallback. The rest of the page streams immediately.

// app/page.tsx
import { Suspense } from 'react'

export default function Page() {
  return (
    <main>
      <h1>Dashboard</h1>
      <Suspense fallback={<p>Loading revenue…</p>}>
        <Revenue />
      </Suspense>
    </main>
  )
}

async function Revenue() {
  const data = await getRevenue() // slow
  return <RevenueChart data={data} />
}

Examples

Use loading.tsx for instant route-level loading UI:

// app/dashboard/loading.tsx
export default function Loading() {
  return <DashboardSkeleton />
}

Stream multiple independent sections so each appears as soon as it is ready:

import { Suspense } from 'react'

export default function Page() {
  return (
    <>
      <Suspense fallback={<CardSkeleton />}>
        <LatestOrders />
      </Suspense>
      <Suspense fallback={<CardSkeleton />}>
        <TopCustomers />
      </Suspense>
    </>
  )
}

Common Mistakes

  • Awaiting slow data in the page component itself, which blocks the whole route
  • Wrapping the entire page in one boundary instead of isolating slow parts
  • Forgetting a key or stable fallback, causing layout shift when content loads
  • Expecting loading.tsx to apply to nested layouts it does not wrap
  • Using Suspense for client-only fetching where it will not stream from the server

See Also

nextjs-data-fetching server-components app-router