Server-Side Rendering and Streaming

This module addresses the challenges and solutions involved in rendering React components on the server, hydrating them on the client, and efficiently managing data fetching and UI updates using streaming and concurrent features such as Suspense. It focuses on enabling performant server-client transitions with partial hydration capabilities.


Purpose and Core Concepts

Server-Side Rendering (SSR) is a technique to render the initial HTML markup of a React application on the server. This provides faster initial page loads and better SEO. However, SSR alone does not solve all performance issues, especially for complex data fetching scenarios and interactive UI elements.

Streaming SSR enhances this by progressively sending HTML chunks to the client as soon as they are ready, improving perceived load times and enabling partial hydration of components. Partial hydration means only some parts of the UI hydrate and become interactive immediately, while others hydrate asynchronously or on-demand, often coordinated with Suspense boundaries.

This module leverages React's concurrent rendering and Suspense mechanism to:


How the Module Works

Partial Hydration with Suspense

Partial hydration is implemented by splitting the UI into Suspense boundaries, some of which suspend rendering until asynchronous data is available. On the server, React renders as much as possible and streams the rest when data resolves. On the client, Suspense boundaries coordinate hydration timing based on data readiness.

The key workflow involves:

  1. Server Render:
    Components fetch data and render initial markup. Suspense boundaries suspend rendering sections that depend on asynchronous data, allowing streaming of content progressively.

  2. Client Hydration:
    The client starts hydration with the parts already rendered. Suspended boundaries show fallback UI until data resolves, then hydrate asynchronously.

  3. Data Fetching Coordination:
    Data fetching hooks (e.g., useSWR) integrate with Suspense, throwing promises to suspend rendering until data is available.

  4. Debugging and State Tracking:
    Hooks like useDebugHistory track data changes and hydration states to assist debugging of hydration flows.

Example: Delayed Hydration in page.tsx

The e2e/site/app/partially-hydrate/page.tsx file demonstrates partial hydration using Suspense with a simulated 2-second delay promise:

let resolved = false
const susp = new Promise(res => {
  setTimeout(() => {
    resolved = true
    res(true)
  }, 2000)
})

export default function Page() {
  if (!resolved) {
    throw susp  // Suspends rendering until the promise resolves
  }

  const { data } = useData()  // Data hook that fetches or reads cached data
  const debugRef = useDebugHistory(data, 'second history:')
  return (
    <div>
      <div ref={debugRef}></div>
      <>second data (delayed hydration):{data || 'undefined'}</>
    </div>
  )
}

Interaction with Other System Components


Design Patterns and Unique Approaches


Key Code References

Suspense Boundary with Delayed Hydration (from page.tsx)

if (!resolved) {
  throw susp  // Suspends rendering until promise resolves
}

This snippet is central to implementing partial hydration by suspending rendering until asynchronous conditions are met.

Debugging Data History Hook Usage

const debugRef = useDebugHistory(data, 'second history:')

This hook tracks the data changes during hydration stages, helping detect mismatches or hydration issues.

SSR Streaming Test Example (from stream-ssr.test.ts)

await page.goto('./partially-hydrate', { waitUntil: 'commit' })

await expect(page.getByText('first data:undefined')).toBeVisible()
await expect(page.getByText('second data (delayed hydration):undefined')).toBeVisible()
await expect(page.getByText('first data:SSR Works')).toBeVisible()
await expect(page.getByText('second data (delayed hydration):SSR Works')).toBeVisible()

This test validates that:


Mermaid Flowchart: Partial Hydration Workflow

flowchart TD
  A[Server Starts Rendering] --> B[Render Components]
  B --> C{Data Ready?}
  C -->|No| D[Throw Promise - Suspend Rendering]
  C -->|Yes| E[Render with Data]
  D --> F[Stream Partial HTML to Client]
  E --> F
  F --> G[Client Receives HTML]
  G --> H[Hydrate Ready Components]
  H --> I{Suspense Boundary Suspended?}
  I -->|Yes| J[Show Fallback UI]
  I -->|No| K[Show Hydrated Content]
  J --> L[Await Data Resolution]
  L --> H
  K --> M[Full Hydration Complete]

This flowchart illustrates the interaction of server rendering with data readiness, streaming partial HTML, client hydration, and Suspense boundaries managing fallback UI and asynchronous hydration.


Summary

The Server-Side Rendering and Streaming module implements sophisticated hydration strategies leveraging React Suspense and concurrent rendering. It enables partial hydration by deferring UI hydration until data is ready, improving performance and user experience. Integrated debugging utilities and rigorous automated tests ensure the correctness and reliability of SSR streaming workflows.