0% found this document useful (0 votes)
4 views

renderToReadableStream – React

The document provides an overview of the `renderToReadableStream` API in React, which allows rendering a React tree to a Readable Web Stream. It details usage, parameters, error handling, and streaming content as it loads, emphasizing the importance of using Suspense for optimal user experience. Additionally, it explains how to manage server errors and set response status codes during rendering.

Uploaded by

dungeon.dad87
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views

renderToReadableStream – React

The document provides an overview of the `renderToReadableStream` API in React, which allows rendering a React tree to a Readable Web Stream. It details usage, parameters, error handling, and streaming content as it loads, emphasizing the importance of using Suspense for optimal user experience. Additionally, it explains how to manage server errors and set response status codes during rendering.

Uploaded by

dungeon.dad87
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 19

20/02/2025, 19:15 renderToReadableStream – React

v19

API REFERENCE SERVER APIS

renderToReadableStream
renderToReadableStream renders a React tree to a Readable Web
Stream.

const stream = await renderToReadableStream(reactNode, options?

Reference

renderToReadableStream(reactNode, options?)

Usage

Rendering a React tree as HTML to a Readable Web Stream


Streaming more content as it loads
Specifying what goes into the shell
Logging crashes on the server
Recovering from errors inside the shell
Recovering from errors outside the shell
Setting the status code
Handling different errors in different ways
Waiting for all content to load for crawlers and static generation
Aborting server rendering

Note

https://react.dev/reference/react-dom/server/renderToReadableStream 1/19
20/02/2025, 19:15 renderToReadableStream – React

This API depends on Web Streams. For Node.js, use


renderToPipeableStream instead.

Reference

renderToReadableStream(reactNode, options?)

Call renderToReadableStream to render your React tree as HTML into a


Readable Web Stream.

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

async function handler(request) {


const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}

On the client, call hydrateRoot to make the server-generated HTML


interactive.

See more examples below.

Parameters

reactNode : A React node you want to render to HTML. For example, a


JSX element like <App /> . It is expected to represent the entire
document, so the App component should render the <html> tag.

optional options : An object with streaming options.

https://react.dev/reference/react-dom/server/renderToReadableStream 2/19
20/02/2025, 19:15 renderToReadableStream – React

optional bootstrapScriptContent : If specified, this string will be


placed in an inline <script> tag.
optional bootstrapScripts : An array of string URLs for the <script>
tags to emit on the page. Use this to include the <script> that calls
hydrateRoot . Omit it if you don’t want to run React on the client at all.

optional bootstrapModules : Like bootstrapScripts , but emits


<script type="module"> instead.

optional identifierPrefix : A string prefix React uses for IDs


generated by useId . Useful to avoid conflicts when using multiple
roots on the same page. Must be the same prefix as passed to
hydrateRoot .

optional namespaceURI : A string with the root namespace URI for the
stream. Defaults to regular HTML. Pass
'http://www.w3.org/2000/svg' for SVG or
'http://www.w3.org/1998/Math/MathML' for MathML.

optional nonce : A nonce string to allow scripts for script-src


Content-Security-Policy.
optional onError : A callback that fires whenever there is a server error,
whether recoverable or not. By default, this only calls console.error .
If you override it to log crash reports, make sure that you still call
console.error . You can also use it to adjust the status code before
the shell is emitted.
optional progressiveChunkSize : The number of bytes in a chunk. Read
more about the default heuristic.
optional signal : An abort signal that lets you abort server rendering
and render the rest on the client.

Returns

renderToReadableStream returns a Promise:

If rendering the shell is successful, that Promise will resolve to a Readable


Web Stream.
If rendering the shell fails, the Promise will be rejected. Use this to output
a fallback shell.

The returned stream has an additional property:

https://react.dev/reference/react-dom/server/renderToReadableStream 3/19
20/02/2025, 19:15 renderToReadableStream – React

allReady : A Promise that resolves when all rendering is complete,


including both the shell and all additional content. You can await
stream.allReady before returning a response for crawlers and static
generation. If you do that, you won’t get any progressive loading. The
stream will contain the final HTML.

Usage

Rendering a React tree as HTML to a Readable Web


Stream

Call renderToReadableStream to render your React tree as HTML into a


Readable Web Stream:

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

async function handler(request) {


const stream = await renderToReadableStream( <App /> , {
bootstrapScripts: ['/main.js']
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}

Along with the root component , you need to provide a list of bootstrap
<script> paths . Your root component should return the entire document

including the root <html> tag.

For example, it might look like this:

export default function App () {


return (
<html>
<head>

https://react.dev/reference/react-dom/server/renderToReadableStream 4/19
20/02/2025, 19:15 renderToReadableStream – React

<meta charSet="utf-8" />


<meta name="viewport" content="width=device-width, initial-scale
<link rel="stylesheet" href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router />
</body>
</html>
);
}

React will inject the doctype and your bootstrap <script> tags into the
resulting HTML stream:

<!DOCTYPE html>
<html>
<!-- ... HTML from your components ... -->
</html>
<script src=" /main.js " async=""></script>

On the client, your bootstrap script should hydrate the entire document with
a call to hydrateRoot :

import { hydrateRoot } from 'react-dom/client';


import App from './App.js';

hydrateRoot(document, <App /> );

This will attach event listeners to the server-generated HTML and make it
interactive.

DEEP DIVE

https://react.dev/reference/react-dom/server/renderToReadableStream 5/19
20/02/2025, 19:15 renderToReadableStream – React

Reading CSS and JS asset paths from the build output

Show Details

Streaming more content as it loads

Streaming allows the user to start seeing the content even before all the data
has loaded on the server. For example, consider a profile page that shows a
cover, a sidebar with friends and photos, and a list of posts:

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Posts />
</ProfileLayout>
);
}

Imagine that loading data for <Posts /> takes some time. Ideally, you’d want
to show the rest of the profile page content to the user without waiting for
the posts. To do this, wrap Posts in a <Suspense> boundary:

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Sidebar>
<Friends />
<Photos />
https://react.dev/reference/react-dom/server/renderToReadableStream 6/19
20/02/2025, 19:15 renderToReadableStream – React

</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}

This tells React to start streaming the HTML before Posts loads its data.
React will send the HTML for the loading fallback ( PostsGlimmer ) first, and
then, when Posts finishes loading its data, React will send the remaining
HTML along with an inline <script> tag that replaces the loading fallback
with that HTML. From the user’s perspective, the page will first appear with
the PostsGlimmer , later replaced by the Posts .

You can further nest <Suspense> boundaries to create a more granular


loading sequence:

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<BigSpinner />}>
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</Suspense>
</ProfileLayout>
);
}

In this example, React can start streaming the page even earlier. Only
ProfileLayout and ProfileCover must finish rendering first because they

https://react.dev/reference/react-dom/server/renderToReadableStream 7/19
20/02/2025, 19:15 renderToReadableStream – React

are not wrapped in any <Suspense> boundary. However, if Sidebar ,


Friends , or Photos need to load some data, React will send the HTML for

the BigSpinner fallback instead. Then, as more data becomes available,


more content will continue to be revealed until all of it becomes visible.

Streaming does not need to wait for React itself to load in the browser, or for
your app to become interactive. The HTML content from the server will get
progressively revealed before any of the <script> tags load.

Read more about how streaming HTML works.

Note

Only Suspense-enabled data sources will activate the Suspense


component. They include:

Data fetching with Suspense-enabled frameworks like Relay and


Next.js
Lazy-loading component code with lazy
Reading the value of a Promise with use

Suspense does not detect when data is fetched inside an Effect or


event handler.

The exact way you would load data in the Posts component above
depends on your framework. If you use a Suspense-enabled
framework, you’ll find the details in its data fetching documentation.

Suspense-enabled data fetching without the use of an opinionated


framework is not yet supported. The requirements for implementing
a Suspense-enabled data source are unstable and undocumented.
An official API for integrating data sources with Suspense will be
released in a future version of React.

https://react.dev/reference/react-dom/server/renderToReadableStream 8/19
20/02/2025, 19:15 renderToReadableStream – React

Specifying what goes into the shell

The part of your app outside of any <Suspense> boundaries is called the
shell:

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<BigSpinner />}>
<Sidebar>
<Friends />
<Photos />
</Sidebar>
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</Suspense>
</ProfileLayout>
);
}

It determines the earliest loading state that the user may see:

<ProfileLayout>
<ProfileCover />
<BigSpinner />
</ProfileLayout>

If you wrap the whole app into a <Suspense> boundary at the root, the shell
will only contain that spinner. However, that’s not a pleasant user experience
because seeing a big spinner on the screen can feel slower and more
annoying than waiting a bit more and seeing the real layout. This is why
usually you’ll want to place the <Suspense> boundaries so that the shell feels
minimal but complete—like a skeleton of the entire page layout.

https://react.dev/reference/react-dom/server/renderToReadableStream 9/19
20/02/2025, 19:15 renderToReadableStream – React

The async call to renderToReadableStream will resolve to a stream as soon


as the entire shell has been rendered. Usually, you’ll start streaming then by
creating and returning a response with that stream :

async function handler(request) {


const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js']
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}

By the time the stream is returned, components in nested <Suspense>


boundaries might still be loading data.

Logging crashes on the server

By default, all errors on the server are logged to console. You can override
this behavior to log crash reports:

async function handler(request) {


const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}

https://react.dev/reference/react-dom/server/renderToReadableStream 10/19
20/02/2025, 19:15 renderToReadableStream – React

If you provide a custom onError implementation, don’t forget to also log


errors to the console like above.

Recovering from errors inside the shell

In this example, the shell contains ProfileLayout , ProfileCover , and


PostsGlimmer :

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}

If an error occurs while rendering those components, React won’t have any
meaningful HTML to send to the client. Wrap your renderToReadableStream
call in a try...catch to send a fallback HTML that doesn’t rely on server
rendering as the last resort:

async function handler(request) {


try {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});

https://react.dev/reference/react-dom/server/renderToReadableStream 11/19
20/02/2025, 19:15 renderToReadableStream – React

} catch (error) {
return new Response('<h1>Something went wrong</h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}

If there is an error while generating the shell, both onError and your catch
block will fire. Use onError for error reporting and use the catch block to
send the fallback HTML document. Your fallback HTML does not have to be
an error page. Instead, you may include an alternative shell that renders your
app on the client only.

Recovering from errors outside the shell

In this example, the <Posts /> component is wrapped in <Suspense> so it is


not a part of the shell:

function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}

If an error happens in the Posts component or somewhere inside it, React


will try to recover from it:

1. It will emit the loading fallback for the closest <Suspense> boundary
( PostsGlimmer ) into the HTML.

https://react.dev/reference/react-dom/server/renderToReadableStream 12/19
20/02/2025, 19:15 renderToReadableStream – React

2. It will “give up” on trying to render the Posts content on the server
anymore.
3. When the JavaScript code loads on the client, React will retry rendering
Posts on the client.

If retrying rendering Posts on the client also fails, React will throw the error
on the client. As with all the errors thrown during rendering, the closest
parent error boundary determines how to present the error to the user. In
practice, this means that the user will see a loading indicator until it is certain
that the error is not recoverable.

If retrying rendering Posts on the client succeeds, the loading fallback from
the server will be replaced with the client rendering output. The user will not
know that there was a server error. However, the server onError callback
and the client onRecoverableError callbacks will fire so that you can get
notified about the error.

Setting the status code

Streaming introduces a tradeoff. You want to start streaming the page as


early as possible so that the user can see the content sooner. However, once
you start streaming, you can no longer set the response status code.

By dividing your app into the shell (above all <Suspense> boundaries) and the
rest of the content, you’ve already solved a part of this problem. If the shell
errors, your catch block will run which lets you set the error status code.
Otherwise, you know that the app may recover on the client, so you can send
“OK”.

async function handler(request) {


try {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
console.error(error);
logServerCrashReport(error);

https://react.dev/reference/react-dom/server/renderToReadableStream 13/19
20/02/2025, 19:15 renderToReadableStream – React

}
});
return new Response(stream, {
status: 200,
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Something went wrong</h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}

If a component outside the shell (i.e. inside a <Suspense> boundary) throws


an error, React will not stop rendering. This means that the onError callback
will fire, but your code will continue running without getting into the catch
block. This is because React will try to recover from that error on the client,
as described above.

However, if you’d like, you can use the fact that something has errored to set
the status code:

async function handler(request) {


try {
let didError = false;
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
status: didError ? 500 : 200,
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Something went wrong</h1>', {

https://react.dev/reference/react-dom/server/renderToReadableStream 14/19
20/02/2025, 19:15 renderToReadableStream – React

status: 500,
headers: { 'content-type': 'text/html' },
});
}
}

This will only catch errors outside the shell that happened while generating
the initial shell content, so it’s not exhaustive. If knowing whether an error
occurred for some content is critical, you can move it up into the shell.

Handling different errors in different ways

You can create your own Error subclasses and use the instanceof operator
to check which error is thrown. For example, you can define a custom
NotFoundError and throw it from your component. Then you can save the

error in onError and do something different before returning the response


depending on the error type:

async function handler(request) {


let didError = false;
let caughtError = null;

function getStatusCode() {
if (didError) {
if (caughtError instanceof NotFoundError) {
return 404;
} else {
return 500;
}
} else {
return 200;
}
}

try {
const stream = await renderToReadableStream(<App />, {
bootstrapScripts: ['/main.js'],
onError(error) {

https://react.dev/reference/react-dom/server/renderToReadableStream 15/19
20/02/2025, 19:15 renderToReadableStream – React

didError = true;
caughtError = error;
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
status: getStatusCode(),
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Something went wrong</h1>', {
status: getStatusCode(),
headers: { 'content-type': 'text/html' },
});
}
}

Keep in mind that once you emit the shell and start streaming, you can’t
change the status code.

Waiting for all content to load for crawlers and static


generation

Streaming offers a better user experience because the user can see the
content as it becomes available.

However, when a crawler visits your page, or if you’re generating the pages at
the build time, you might want to let all of the content load first and then
produce the final HTML output instead of revealing it progressively.

You can wait for all the content to load by awaiting the stream.allReady
Promise:

async function handler(request) {


try {
let didError = false;
const stream = await renderToReadableStream(<App />, {

https://react.dev/reference/react-dom/server/renderToReadableStream 16/19
20/02/2025, 19:15 renderToReadableStream – React

bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
let isCrawler = // ... depends on your bot detection strategy ...
if (isCrawler) {
await stream.allReady;
}
return new Response(stream, {
status: didError ? 500 : 200,
headers: { 'content-type': 'text/html' },
});
} catch (error) {
return new Response('<h1>Something went wrong</h1>', {
status: 500,
headers: { 'content-type': 'text/html' },
});
}
}

A regular visitor will get a stream of progressively loaded content. A crawler


will receive the final HTML output after all the data loads. However, this also
means that the crawler will have to wait for all data, some of which might be
slow to load or error. Depending on your app, you could choose to send the
shell to the crawlers too.

Aborting server rendering

You can force the server rendering to “give up” after a timeout:

async function handler(request) {


try {
const controller = new AbortController();
setTimeout(() => {
controller.abort();

https://react.dev/reference/react-dom/server/renderToReadableStream 17/19
20/02/2025, 19:15 renderToReadableStream – React

}, 10000);

const stream = await renderToReadableStream(<App />, {


signal: controller.signal,
bootstrapScripts: ['/main.js'],
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});
// ...

React will flush the remaining loading fallbacks as HTML, and will attempt to
render the rest on the client.

PREVIOUS

renderToPipeableStream

NEXT

renderToStaticMarkup

Copyright © Meta Platforms, Inc

uwu?

Learn React API Reference

Quick Start React APIs

Installation React DOM APIs

Describing the UI

https://react.dev/reference/react-dom/server/renderToReadableStream 18/19
20/02/2025, 19:15 renderToReadableStream – React

Adding Interactivity

Managing State

Escape Hatches

Community More

Code of Conduct Blog

Meet the Team React Native

Docs Contributors Privacy

Acknowledgements Terms

https://react.dev/reference/react-dom/server/renderToReadableStream 19/19

You might also like