renderToReadableStream
renderToReadableStream renders a React tree to a ReadableStream of HTML using the Web Streams API. This is the recommended approach for edge runtimes, Cloudflare Workers, Deno, and other modern JavaScript environments.
This API uses Web Streams API (
ReadableStream) instead of Node.js streams. For Node.js, use renderToPipeableStream instead.Reference
renderToReadableStream(element, options?)
Renders a React element to a ReadableStream with full Suspense support and progressive streaming.
Parameters
element:ReactNode- The React element to renderoptions:Object(optional)identifierPrefix:string- Prefix for IDs generated byuseIdnamespaceURI:string- Namespace URI for the document (e.g., SVG)nonce:string | { script?: string, style?: string }- Nonce for Content Security PolicybootstrapScriptContent:string- Inline script to run before other scriptsbootstrapScripts:Array<string | { src: string, integrity?: string, crossOrigin?: string }>- External scripts to loadbootstrapModules:Array<string | { src: string, integrity?: string, crossOrigin?: string }>- External modules to loadprogressiveChunkSize:number- Size of chunks for progressive streamingsignal:AbortSignal- Signal to abort the renderonError:(error: mixed, errorInfo: ErrorInfo) => ?string- Error handler callbackimportMap:ImportMap- Import map for module scriptsformState:ReactFormState | null- Form state for progressive enhancementonHeaders:(headers: Headers) => void- Callback when headers are readymaxHeadersLength:number- Maximum length for early hints headers
Returns
Returns aPromise that resolves to a ReactDOMServerReadableStream:
- The Promise resolves when the shell (initial UI) is ready
- The stream continues emitting chunks as Suspense boundaries resolve
stream.allReadyresolves when all content, including Suspense boundaries, is complete
Caveats
Usage
Basic streaming with edge runtime
- Vercel Edge
- Cloudflare Workers
- Deno
Streaming with Suspense
Error handling
Aborting renders
UseAbortSignal to cancel rendering when the client disconnects:
Waiting for all content (SEO)
Wait forallReady when serving content to search engine crawlers:
Custom headers with onHeaders
Advanced Patterns
Streaming with transformations
Streaming with compression
Streaming HTML with document wrapper
Runtime Differences
- Edge Runtime
- Node.js
- Bun
- Vercel Edge Functions
- Cloudflare Workers
- Deno Deploy
- Edge runtimes without Node.js APIs
Common Issues
Stream already locked/consumed
Stream already locked/consumed
ReadableStream can only be read once. Don’t try to use the same stream multiple times:Headers already sent error
Headers already sent error
When using
onHeaders, ensure you’re setting headers before creating the Response:Shell rendering fails with rejected Promise
Shell rendering fails with rejected Promise
If rendering fails before the shell is ready, the Promise rejects:
allReady never resolves
allReady never resolves
If a Suspense boundary never resolves, Solution: Add timeouts and proper error handling for async operations.
allReady will hang:Performance Best Practices
Stream immediately for best TTFB
Stream immediately for best TTFB
Start streaming as soon as the shell is ready:
Use progressive chunk size
Use progressive chunk size
Control streaming granularity with Smaller = more granular streaming, larger = fewer chunks
progressiveChunkSize:Avoid blocking in Suspense boundaries
Avoid blocking in Suspense boundaries
Keep Suspense boundaries small and focused:
See Also
renderToPipeableStream- Streaming API for Node.js- Server Components - Build with async server components
- Server Rendering Overview - Compare all server rendering approaches