Skip to main content

Server Rendering Overview

React provides several APIs for rendering React components on the server. These APIs are exported from react-dom/server and enable server-side rendering (SSR), static site generation (SSG), and streaming HTML to the client.

When to Use Server Rendering

Server rendering is useful when you need to:
  • Improve initial page load performance - Send HTML to the client immediately, allowing users to see content before JavaScript loads
  • Enhance SEO - Search engines can crawl and index your content more effectively
  • Support devices with limited JavaScript - Provide a baseline experience on devices where JavaScript may be disabled or slow
  • Enable progressive enhancement - Start with working HTML and enhance with JavaScript as it loads

Server Rendering APIs

React provides different server rendering APIs for different use cases:

Legacy APIs (Synchronous)

renderToString

Synchronously render React components to an HTML string with React attributes

renderToStaticMarkup

Synchronously render React components to static HTML without React attributes
Limitations:
  • No Suspense support
  • Blocking - must wait for entire tree to render
  • No streaming capabilities
  • Not recommended for new projects

Modern APIs (Streaming)

renderToPipeableStream

Stream HTML to a Node.js Writable stream with full Suspense support

renderToReadableStream

Stream HTML using Web Streams API for edge runtimes and browsers
Benefits:
  • Full Suspense support
  • Progressive streaming - send HTML as it becomes ready
  • Better user experience with selective hydration
  • Error boundaries work correctly
  • Recommended for all new projects

Runtime Differences

React server APIs work across different JavaScript runtimes:
import { renderToPipeableStream } from 'react-dom/server';
// or explicitly:
import { renderToPipeableStream } from 'react-dom/server.node';

// Uses Node.js streams (Writable)
const { pipe } = renderToPipeableStream(<App />, {
  onShellReady() {
    pipe(res); // res is a Node.js response stream
  }
});
The correct export is automatically selected based on your environment:
  • react-dom/server.node - Node.js environments (default)
  • react-dom/server.edge - Edge runtimes (Vercel Edge, Cloudflare Workers)
  • react-dom/server.bun - Bun runtime
  • react-dom/server.browser - Browser environments (rare)

SSR vs SSG

Server-Side Rendering (SSR)

Render HTML on-demand for each request:
app.get('*', async (req, res) => {
  const stream = await renderToReadableStream(<App url={req.url} />);
  stream.pipeTo(res);
});
When to use:
  • Content changes frequently
  • Personalized content per user
  • Real-time data

Static Site Generation (SSG)

Pre-render HTML at build time:
import { renderToStaticMarkup } from 'react-dom/server';

const html = renderToStaticMarkup(<App />);
fs.writeFileSync('index.html', html);
When to use:
  • Content rarely changes
  • Same content for all users
  • Maximum performance

Streaming Architecture

Streaming allows you to send HTML to the client in chunks as it becomes ready:
┌─────────────────────────────────────────┐
│  Server renders React tree              │
│                                         │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐│
│  │ Shell   │  │ Content │  │ Suspense││
│  │ (ready) │→ │ (ready) │→ │ (wait)  ││
│  └────┬────┘  └────┬────┘  └────┬────┘│
│       │            │             │     │
└───────┼────────────┼─────────────┼─────┘
        ↓            ↓             ↓
   Send HTML    Send HTML    Send when ready
   immediately   chunk        with inline <script>

Shell vs Content

  • Shell: Initial UI structure, sent on onShellReady
  • Content: Everything else, streamed progressively
  • Suspense boundaries: Wait for async data, stream when ready
<Layout> {/* Shell - sent immediately */}
  <Suspense fallback={<Spinner />}>
    <AsyncContent /> {/* Streamed when ready */}
  </Suspense>
</Layout>

Server Components

React Server Components let you write components that run only on the server:
// This component runs ONLY on the server
async function UserProfile({ userId }) {
  // Direct database access - no API needed
  const user = await db.users.findById(userId);
  
  return <div>{user.name}</div>;
}
See Server Components for detailed documentation.

Error Handling

All streaming APIs support error handling callbacks:
const stream = await renderToReadableStream(<App />, {
  onError(error, errorInfo) {
    console.error('Rendering error:', error);
    logErrorToService(error, errorInfo);
    
    // Return a digest to send to client
    return errorInfo.digest;
  }
});

Best Practices

renderToPipeableStream and renderToReadableStream provide better performance and user experience than legacy renderToString.
Use Error Boundaries to catch and handle errors gracefully during server rendering.
Place Suspense boundaries strategically to balance initial content delivery with progressive loading.
Start streaming in onShellReady to minimize time to first byte for users.
For SEO purposes, wait for onAllReady when serving content to search engine crawlers.

Next Steps

renderToString

Learn about synchronous HTML generation

renderToPipeableStream

Implement streaming SSR in Node.js

renderToReadableStream

Use Web Streams API for edge runtimes

Server Components

Build with React Server Components