Streaming SSR – Progressive Rendering

Rendering & Architecture

Understanding Streaming SSR

Traditional Server-Side Rendering (SSR) has a "waterfall" problem: the server must fetch all data, render the entire HTML string, and only then send it to the browser. This means if one slow API call takes 2 seconds, the user stares at a blank white screen for 2 seconds.

Streaming SSR solves this by breaking the page into smaller chunks. The server sends the static parts of the page (like the header and sidebar) immediately, and then "streams" the dynamic content (like a product list or comments) as soon as it's ready.

The Streaming Execution Flow

  1. Request: The user requests a page.
  2. Instant Response: The server immediately sends the initial HTML shell (layout, loading skeletons).
  3. Parallel Fetching: The server fetches data for different components in parallel.
  4. Streaming Chunks: As each component finishes data fetching and rendering, its HTML is streamed to the browser and inserted into the correct place.
  5. Interactive: React hydrates these chunks as they arrive, making them interactive even while other parts of the page are still loading.

Why Use Streaming SSR?

Streaming is the modern evolution of SSR, designed to fix the "all-or-nothing" blocking nature of traditional server rendering.

1. Faster Time to First Byte (TTFB)

Since the server doesn't wait for the slowest data fetch to complete, it can start sending HTML almost immediately. This significantly improves TTFB, a key metric for perceived performance.

2. Improved First Contentful Paint (FCP)

Users see the page structure (headers, navigation, skeletons) instantly. This visual feedback reduces the bounce rate because users know something is happening, unlike a blank screen which might make them think the site is broken.

3. Selective Hydration

React 18 and Next.js allow for "Selective Hydration." This means if a user clicks on a component that has loaded (e.g., a sidebar menu) while the main content is still streaming, React will prioritize hydrating that interaction first.


🛠️ Implementing Streaming in Next.js

Next.js App Router is built on top of React Server Components and Streaming by default. You implement it using React Suspense.

1. Using loading.tsx (Page Level)

The easiest way to stream a whole page is to add a loading.tsx file in your route segment. Next.js will automatically wrap your page.tsx in a Suspense boundary.

app/dashboard/loading.tsx
// app/dashboard/loading.tsx
export default function Loading() {
  // You can add any UI inside Loading, including a Skeleton.
  return <LoadingSkeleton />;
}

2. Using <Suspense> (Component Level)

For more granular control, you can wrap individual components in <Suspense>. This allows parts of the page to load independently.

app/dashboard/page.tsx
// app/dashboard/page.tsx
import { Suspense } from "react";
import { RevenueChart } from "@/components/RevenueChart";
import { LatestInvoices } from "@/components/LatestInvoices";
import {
  CardsSkeleton,
  RevenueChartSkeleton,
  InvoiceSkeleton,
} from "@/components/skeletons";
 
export default async function DashboardPage() {
  return (
    <main>
      <h1 className="text-xl md:text-2xl">Dashboard</h1>
 
      <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
        <Suspense fallback={<CardsSkeleton />}>
          {/* @ts-expect-error Async Server Component */}
          <CardWrapper />
        </Suspense>
      </div>
 
      <div className="mt-6 grid grid-cols-1 gap-6 md:grid-cols-4 lg:grid-cols-8">
        {/* The RevenueChart can load independently from LatestInvoices */}
        <Suspense fallback={<RevenueChartSkeleton />}>
          {/* @ts-expect-error Async Server Component */}
          <RevenueChart />
        </Suspense>
 
        <Suspense fallback={<InvoiceSkeleton />}>
          {/* @ts-expect-error Async Server Component */}
          <LatestInvoices />
        </Suspense>
      </div>
    </main>
  );
}

In this example:

  1. The DashboardPage shell renders immediately.
  2. RevenueChart and LatestInvoices start fetching data in parallel.
  3. If RevenueChart finishes first, it streams in and replaces <RevenueChartSkeleton />.
  4. If LatestInvoices is slower, it continues to show <InvoiceSkeleton /> until ready.

Conclusion: The Future of Rendering 🔮

Streaming SSR represents a paradigm shift in how we build web applications. It moves us away from the binary choice of "Client vs. Server" and towards a fluid, hybrid model where the server sends what it can, when it can.

By leveraging React Suspense and Next.js App Router, you can deliver experiences that feel instant, handle slow data sources gracefully, and keep your users engaged from the very first byte.