renderToReadableStream – React
renderToReadableStream – React
v19
renderToReadableStream
renderToReadableStream renders a React tree to a Readable Web
Stream.
Reference
renderToReadableStream(reactNode, options?)
Usage
Note
https://react.dev/reference/react-dom/server/renderToReadableStream 1/19
20/02/2025, 19:15 renderToReadableStream – React
Reference
renderToReadableStream(reactNode, options?)
Parameters
https://react.dev/reference/react-dom/server/renderToReadableStream 2/19
20/02/2025, 19:15 renderToReadableStream – React
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.
Returns
https://react.dev/reference/react-dom/server/renderToReadableStream 3/19
20/02/2025, 19:15 renderToReadableStream – React
Usage
Along with the root component , you need to provide a list of bootstrap
<script> paths . Your root component should return the entire document
https://react.dev/reference/react-dom/server/renderToReadableStream 4/19
20/02/2025, 19:15 renderToReadableStream – React
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 :
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
Show Details
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 .
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
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.
Note
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.
https://react.dev/reference/react-dom/server/renderToReadableStream 8/19
20/02/2025, 19:15 renderToReadableStream – React
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
By default, all errors on the server are logged to console. You can override
this behavior to log crash reports:
https://react.dev/reference/react-dom/server/renderToReadableStream 10/19
20/02/2025, 19:15 renderToReadableStream – React
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:
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.
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}
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.
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”.
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' },
});
}
}
However, if you’d like, you can use the fact that something has errored to set
the status code:
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.
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
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.
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:
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' },
});
}
}
You can force the server rendering to “give up” after a timeout:
https://react.dev/reference/react-dom/server/renderToReadableStream 17/19
20/02/2025, 19:15 renderToReadableStream – React
}, 10000);
React will flush the remaining loading fallbacks as HTML, and will attempt to
render the rest on the client.
PREVIOUS
renderToPipeableStream
NEXT
renderToStaticMarkup
uwu?
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
Acknowledgements Terms
https://react.dev/reference/react-dom/server/renderToReadableStream 19/19