# Server-Side Rendering

Streaming server-side rendering APIs optimized for different JavaScript runtimes with full support for Suspense, Server Components, and progressive hydration.

## Runtime Selection

React DOM automatically selects the appropriate runtime implementation:

- **Node.js**: `react-dom/server` or `react-dom/server.node`
- **Browser/Deno**: `react-dom/server.browser`
- **Edge Runtimes**: `react-dom/server.edge` (Cloudflare Workers, Vercel Edge)
- **Bun**: `react-dom/server.bun`

Import from `react-dom/server` for automatic selection, or use runtime-specific entry points for explicit control.

## Capabilities

### Node.js Streaming - renderToPipeableStream

Renders React elements to a pipeable Node.js stream for server-side rendering with Suspense support.

```javascript { .api }
/**
 * Render to Node.js pipeable stream (primary SSR API for Node.js)
 * @param children - React elements to render
 * @param options - Configuration options
 * @returns PipeableStream object
 */
function renderToPipeableStream(
  children: ReactNode,
  options?: RenderToPipeableStreamOptions
): PipeableStream;

interface RenderToPipeableStreamOptions {
  /** Prefix for IDs generated by useId */
  identifierPrefix?: string;
  /** Namespace URI for SVG or MathML content */
  namespaceURI?: string;
  /** CSP nonce for inline scripts and styles */
  nonce?: string | { script?: string; style?: string };
  /** Inline script content to inject before React code */
  bootstrapScriptContent?: string;
  /** External script URLs to load */
  bootstrapScripts?: Array<string | BootstrapScriptDescriptor>;
  /** ES module URLs to load */
  bootstrapModules?: Array<string | BootstrapScriptDescriptor>;
  /** Target chunk size for progressive streaming (bytes) */
  progressiveChunkSize?: number;
  /** Called when initial shell is ready to be sent */
  onShellReady?: () => void;
  /** Called if shell rendering encounters an error */
  onShellError?: (error: Error) => void;
  /** Called when all content including Suspense is ready */
  onAllReady?: () => void;
  /** Error handler called for all errors */
  onError?: (error: Error, errorInfo: ErrorInfo) => string | void;
  /** Called when rendering is postponed (static generation) */
  onPostpone?: (reason: string, postponeInfo: PostponeInfo) => void;
  /** External runtime script for React */
  unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor;
  /** Import map for module resolution */
  importMap?: ImportMap;
  /** Form state for Server Actions */
  formState?: ReactFormState;
  /** Called with headers that should be set */
  onHeaders?: (headers: Headers) => void;
  /** Maximum length for header values */
  maxHeadersLength?: number;
}

interface PipeableStream {
  /** Pipe to a Node.js writable stream */
  pipe<T extends Writable>(destination: T): T;
  /** Abort rendering and streaming */
  abort(reason?: string): void;
}

interface BootstrapScriptDescriptor {
  src: string;
  integrity?: string;
  crossOrigin?: 'anonymous' | 'use-credentials';
}

interface ErrorInfo {
  componentStack?: string;
}

interface PostponeInfo {
  componentStack?: string;
}
```

**Usage Examples:**

```javascript
import { renderToPipeableStream } from 'react-dom/server';
import { createServer } from 'http';

// Basic usage
createServer((req, res) => {
  const stream = renderToPipeableStream(<App />, {
    onShellReady() {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/html');
      stream.pipe(res);
    },
    onError(error) {
      console.error(error);
      res.statusCode = 500;
      res.end('Internal Server Error');
    }
  });
}).listen(3000);

// With bootstrapping and error handling
const stream = renderToPipeableStream(<App url={req.url} />, {
  bootstrapScripts: ['/client.js'],
  bootstrapModules: ['/app.js'],
  identifierPrefix: 'app-',
  onShellReady() {
    // Shell (above Suspense boundaries) is ready
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/html');
    stream.pipe(res);
  },
  onShellError(error) {
    // Error in initial shell before Suspense
    res.statusCode = 500;
    res.setHeader('Content-Type', 'text/html');
    res.send('<h1>Server Error</h1>');
  },
  onError(error, errorInfo) {
    // Log errors but continue streaming
    console.error('SSR Error:', error);
    console.error('Component stack:', errorInfo.componentStack);
  }
});

// Wait for all content (including Suspense)
const stream = renderToPipeableStream(<App />, {
  onAllReady() {
    // Everything including Suspense boundaries is ready
    // Use for crawlers/bots that need complete content
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/html');
    stream.pipe(res);
  },
  onShellError(error) {
    res.statusCode = 500;
    res.end();
  }
});

// Aborting
const stream = renderToPipeableStream(<App />, options);
setTimeout(() => {
  stream.abort('Timeout');
}, 10000);
```

**Streaming Strategy:**

```javascript
// For users: Stream shell immediately, lazy-load rest
onShellReady() {
  stream.pipe(res);  // Fast initial response
}

// For bots: Wait for all content
onAllReady() {
  stream.pipe(res);  // Complete HTML for SEO
}
```

### Web Streams - renderToReadableStream

Renders React elements to a Web Streams API ReadableStream for environments supporting the standard.

```javascript { .api }
/**
 * Render to Web Streams API ReadableStream
 * @param children - React elements to render
 * @param options - Configuration options
 * @returns Promise resolving to ReadableStream with allReady property
 */
function renderToReadableStream(
  children: ReactNode,
  options?: RenderToReadableStreamOptions
): Promise<ReadableStream & { allReady: Promise<void> }>;

interface RenderToReadableStreamOptions {
  /** Prefix for IDs generated by useId */
  identifierPrefix?: string;
  /** Namespace URI for SVG or MathML content */
  namespaceURI?: string;
  /** CSP nonce for inline scripts and styles */
  nonce?: string | { script?: string; style?: string };
  /** Inline script content to inject before React code */
  bootstrapScriptContent?: string;
  /** External script URLs to load */
  bootstrapScripts?: Array<string | BootstrapScriptDescriptor>;
  /** ES module URLs to load */
  bootstrapModules?: Array<string | BootstrapScriptDescriptor>;
  /** Target chunk size for progressive streaming (bytes) */
  progressiveChunkSize?: number;
  /** AbortSignal to cancel rendering */
  signal?: AbortSignal;
  /** Error handler called for all errors */
  onError?: (error: Error, errorInfo: ErrorInfo) => string | void;
  /** Called when rendering is postponed */
  onPostpone?: (reason: string, postponeInfo: PostponeInfo) => void;
  /** External runtime script for React */
  unstable_externalRuntimeSrc?: string | BootstrapScriptDescriptor;
  /** Import map for module resolution */
  importMap?: ImportMap;
  /** Form state for Server Actions */
  formState?: ReactFormState;
  /** Called with headers that should be set */
  onHeaders?: (headers: Headers) => void;
  /** Maximum length for header values */
  maxHeadersLength?: number;
}
```

**Usage Examples:**

```javascript
import { renderToReadableStream } from 'react-dom/server';

// Deno HTTP server
Deno.serve(async (req) => {
  const stream = await renderToReadableStream(<App url={req.url} />, {
    bootstrapModules: ['/app.js'],
    onError(error) {
      console.error('SSR Error:', error);
    }
  });

  return new Response(stream, {
    status: 200,
    headers: { 'Content-Type': 'text/html' }
  });
});

// Edge runtime (Cloudflare Workers, Vercel Edge)
export default {
  async fetch(request) {
    const stream = await renderToReadableStream(<App />, {
      bootstrapModules: ['/app.js']
    });

    return new Response(stream, {
      headers: { 'Content-Type': 'text/html' }
    });
  }
};

// Wait for all content
const stream = await renderToReadableStream(<App />, {
  signal: AbortSignal.timeout(10000)
});

// Wait for Suspense boundaries
await stream.allReady;

return new Response(stream, {
  headers: { 'Content-Type': 'text/html' }
});

// With error handling
try {
  const stream = await renderToReadableStream(<App />, {
    onError(error, errorInfo) {
      console.error('Render error:', error);
      logToService(error, errorInfo.componentStack);
    }
  });

  return new Response(stream, {
    status: 200,
    headers: { 'Content-Type': 'text/html' }
  });
} catch (error) {
  return new Response('<h1>Error</h1>', {
    status: 500,
    headers: { 'Content-Type': 'text/html' }
  });
}
```

**Notes:**

- Returns a Promise that resolves when initial shell is ready
- The stream has an `allReady` property that resolves when all Suspense boundaries are done
- Ideal for edge runtimes and modern JavaScript environments
- Use `signal` option with AbortSignal for timeout control

### Resume Rendering - resumeToPipeableStream

Resumes rendering from a postponed state (used in static site generation workflows).

```javascript { .api }
/**
 * Resume rendering to pipeable stream from postponed state
 * @param children - React elements to render
 * @param postponedState - State from previous postponed render
 * @param options - Configuration options
 * @returns PipeableStream object
 */
function resumeToPipeableStream(
  children: ReactNode,
  postponedState: PostponedState,
  options?: RenderToPipeableStreamOptions
): PipeableStream;

type PostponedState = OpaquePostponedState; // Opaque type from prerender
```

**Usage Example:**

```javascript
import { resumeToPipeableStream } from 'react-dom/server';

// Resume from postponed state (typically from static generation)
export function handler(req, res, postponedState) {
  const stream = resumeToPipeableStream(
    <App url={req.url} />,
    postponedState,
    {
      bootstrapScripts: ['/client.js'],
      onShellReady() {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/html');
        stream.pipe(res);
      },
      onError(error) {
        console.error('Resume error:', error);
      }
    }
  );
}
```

**Use Cases:**

- Completing partial static renders at request time
- Hybrid static + dynamic rendering strategies
- Postponing expensive computations until request time

### Resume to ReadableStream - resume

Resumes rendering to ReadableStream from postponed state.

```javascript { .api }
/**
 * Resume rendering to ReadableStream from postponed state
 * @param children - React elements to render
 * @param postponedState - State from previous postponed render
 * @param options - Configuration options
 * @returns Promise resolving to ReadableStream
 */
function resume(
  children: ReactNode,
  postponedState: PostponedState,
  options?: RenderToReadableStreamOptions
): Promise<ReadableStream & { allReady: Promise<void> }>;
```

**Usage Example:**

```javascript
import { resume } from 'react-dom/server';

// Edge function resuming from postponed state
export default {
  async fetch(request, env) {
    const postponedState = await env.KV.get('postponed-state', 'json');

    const stream = await resume(<App />, postponedState, {
      bootstrapModules: ['/app.js']
    });

    return new Response(stream, {
      headers: { 'Content-Type': 'text/html' }
    });
  }
};
```

## Static Prerendering from Server Entry Points

**Note**: The `prerender`, `resumeAndPrerender`, `prerenderToNodeStream`, and `resumeAndPrerenderToNodeStream` functions are also exported from server entry points (`react-dom/server.node`, `react-dom/server.browser`, `react-dom/server.edge`) for convenience. These functions are primarily for static site generation workflows.

For complete documentation of these static prerendering APIs, see [Static Site Generation](./static-generation.md).

## Legacy APIs (Deprecated)

### renderToString

```javascript { .api }
/**
 * Deprecated: Render to HTML string synchronously
 * @param children - React elements to render
 * @returns HTML string
 * @deprecated Use renderToPipeableStream or renderToReadableStream instead
 */
function renderToString(children: ReactNode): string;
```

**Why Deprecated:**

- Blocks the server thread (synchronous)
- No Suspense support
- No streaming
- Slower time to first byte (TTFB)
- All async data must be loaded before rendering starts

**Migration:**

```javascript
// Old
const html = renderToString(<App />);
res.send(html);

// New
const stream = renderToPipeableStream(<App />, {
  onShellReady() {
    stream.pipe(res);
  }
});
```

### renderToStaticMarkup

```javascript { .api }
/**
 * Deprecated: Render to static HTML without React attributes
 * @param children - React elements to render
 * @returns HTML string without React internal attributes
 * @deprecated Use streaming APIs for better performance
 */
function renderToStaticMarkup(children: ReactNode): string;
```

**Use Case:**

- Generating static HTML emails
- Creating static pages without hydration

**Why Deprecated:**

- Same issues as renderToString
- Output cannot be hydrated

**Migration:**

For static HTML generation, use the static prerendering APIs from `react-dom/static` instead.

## Suspense and Streaming

### How Streaming Works

```javascript
import { Suspense } from 'react';

function App() {
  return (
    <html>
      <body>
        <Header />
        <Suspense fallback={<Spinner />}>
          <Comments /> {/* Async data */}
        </Suspense>
        <Footer />
      </body>
    </html>
  );
}

// Streaming sequence:
// 1. Send: <html><body><Header/><Spinner/>
// 2. When Comments ready: Send Comments + script to replace Spinner
// 3. Send: <Footer/></body></html>
```

**Benefits:**

- Fast initial page load (shell renders first)
- Progressive content loading
- Better perceived performance
- Parallel data fetching

### Selective Hydration

```javascript
import { Suspense } from 'react';

function App() {
  return (
    <div>
      <NavBar />
      <Suspense fallback={<Spinner />}>
        <Comments />
      </Suspense>
      <Suspense fallback={<Sidebar />}>
        <RightColumn />
      </Suspense>
    </div>
  );
}
```

**Hydration Order:**

1. NavBar hydrates first (no Suspense)
2. Comments and RightColumn hydrate as they stream in
3. User interactions prioritize hydration of that component
4. Non-interactive sections can hydrate last

## Error Handling Strategies

### Graceful Degradation

```javascript
const stream = renderToPipeableStream(<App />, {
  onError(error, errorInfo) {
    // Log but continue streaming
    console.error('Component error:', error);
    logToMonitoring(error, errorInfo.componentStack);

    // Return error message to include in HTML
    return `<!-- Error: ${error.message} -->`;
  },
  onShellError(error) {
    // Shell failed - send fallback HTML
    res.statusCode = 500;
    res.send('<h1>Something went wrong</h1>');
  }
});
```

### Error Boundaries

```javascript
import { Component } from 'react';

class ErrorBoundary extends Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    logError(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h2>Something went wrong.</h2>;
    }
    return this.props.children;
  }
}

// Use in SSR
function App() {
  return (
    <ErrorBoundary>
      <Suspense fallback={<Loading />}>
        <AsyncContent />
      </Suspense>
    </ErrorBoundary>
  );
}
```

## Bootstrap Scripts

### Loading Client JavaScript

```javascript
renderToPipeableStream(<App />, {
  // Classic scripts (blocking)
  bootstrapScripts: ['/static/js/client.js'],

  // ES modules (non-blocking)
  bootstrapModules: [
    '/static/js/app.js',
    {
      src: '/static/js/vendor.js',
      integrity: 'sha384-...',
      crossOrigin: 'anonymous'
    }
  ],

  // Inline script
  bootstrapScriptContent: 'window.ENV = "production"',

  // External React runtime
  unstable_externalRuntimeSrc: '/static/js/react-runtime.js'
});
```

**Output:**

```html
<!DOCTYPE html>
<html>
  <head>
    <script>window.ENV = "production"</script>
  </head>
  <body>
    <div id="root">...</div>
    <script src="/static/js/client.js"></script>
    <script type="module" src="/static/js/app.js"></script>
    <script
      type="module"
      src="/static/js/vendor.js"
      integrity="sha384-..."
      crossorigin="anonymous"
    ></script>
  </body>
</html>
```

## Best Practices

1. **Use onShellReady for users**: Start streaming as soon as the shell is ready for better perceived performance
2. **Use onAllReady for crawlers**: Wait for all content for SEO-critical pages
3. **Configure onError**: Always log errors even if streaming continues
4. **Handle onShellError**: Provide fallback HTML when the shell fails
5. **Use Suspense boundaries**: Break up async dependencies for progressive loading
6. **Configure bootstrapScripts**: Ensure client-side hydration code loads
7. **Set proper identifierPrefix**: Avoid ID collisions with multiple React roots
8. **Use abort for timeouts**: Prevent indefinite hanging with `abort()`
9. **Handle postpone**: Implement `onPostpone` for static generation workflows
10. **Monitor TTFB**: Optimize shell content to minimize time to first byte

## Runtime-Specific Notes

### Node.js
- Use `renderToPipeableStream` for best performance
- Supports back-pressure with `pipe()`
- Can use `onHeaders` for setting HTTP headers

### Browser/Deno
- Use `renderToReadableStream`
- Works with standard Web Streams API
- Compatible with Service Workers

### Edge Runtimes
- Use `react-dom/server.edge`
- Optimized for Cloudflare Workers, Vercel Edge
- Limited execution time - handle with `signal`

### Bun
- Use `react-dom/server.bun`
- Native ReadableStream support
- High performance rendering

## Version

```javascript { .api }
const version: string; // "19.2.0"
```

## Types

### ImportMap

```javascript { .api }
interface ImportMap {
  imports?: Record<string, string>;
  scopes?: Record<string, Record<string, string>>;
}
```

### ReactFormState

```javascript { .api }
interface ReactFormState<S = any, P = any> {
  [key: string]: any;
}
```

### Headers

```javascript { .api }
interface Headers {
  [key: string]: string | string[];
}
```
