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

renderToPipeableStream – React

The document provides an overview of the `renderToPipeableStream` API in React, which allows rendering a React tree to a Node.js Stream. It details the usage, parameters, and callbacks for handling server rendering, error recovery, and streaming content progressively. Additionally, it explains how to set up the server response and manage loading states with Suspense boundaries for better user experience.

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)
3 views

renderToPipeableStream – React

The document provides an overview of the `renderToPipeableStream` API in React, which allows rendering a React tree to a Node.js Stream. It details the usage, parameters, and callbacks for handling server rendering, error recovery, and streaming content progressively. Additionally, it explains how to set up the server response and manage loading states with Suspense boundaries for better user experience.

Uploaded by

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

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

v19

API REFERENCE SERVER APIS

renderToPipeableStream
renderToPipeableStream renders a React tree to a pipeable
Node.js Stream.

const { pipe, abort } = renderToPipeableStream(reactNode, optio

Reference

renderToPipeableStream(reactNode, options?)

Usage

Rendering a React tree as HTML to a Node.js 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

This API is specific to Node.js. Environments with Web Streams, like


Deno and modern edge runtimes, should use

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

renderToReadableStream instead.

Reference

renderToPipeableStream(reactNode, options?)

Call renderToPipeableStream to render your React tree as HTML into a


Node.js Stream.

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

const { pipe } = renderToPipeableStream(<App />, {


bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});

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.

optional bootstrapScriptContent : If specified, this string will be


placed in an inline <script> tag.

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

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 onAllReady : A callback that fires when all rendering is
complete, including both the shell and all additional content. You can
use this instead of onShellReady for crawlers and static generation. If
you start streaming here, you won’t get any progressive loading. The
stream will contain the final HTML.
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 onShellReady : A callback that fires right after the initial shell
has been rendered. You can set the status code and call pipe here to
start streaming. React will stream the additional content after the shell
along with the inline <script> tags that replace the HTML loading
fallbacks with the content.
optional onShellError : A callback that fires if there was an error
rendering the initial shell. It receives the error as an argument. No bytes
were emitted from the stream yet, and neither onShellReady nor
onAllReady will get called, so you can output a fallback HTML shell.

optional progressiveChunkSize : The number of bytes in a chunk. Read


more about the default heuristic.

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

Returns

renderToPipeableStream returns an object with two methods:

pipe outputs the HTML into the provided Writable Node.js Stream. Call
pipe in onShellReady if you want to enable streaming, or in onAllReady
for crawlers and static generation.
abort lets you abort server rendering and render the rest on the client.

Usage

Rendering a React tree as HTML to a Node.js Stream

Call renderToPipeableStream to render your React tree as HTML into a


Node.js Stream:

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

// The route handler syntax depends on your backend framework


app.use('/', (request, response) => {
const { pipe } = renderToPipeableStream( <App /> , {
bootstrapScripts: ['/main.js'] ,
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});
});

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:

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

export default function App () {


return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1
<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.
https://react.dev/reference/react-dom/server/renderToPipeableStream 5/18
20/02/2025, 19:15 renderToPipeableStream – React

DEEP DIVE

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>

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

<ProfileCover />
<Sidebar>
<Friends />
<Photos />
</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>
);
}

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

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

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/renderToPipeableStream 8/18
20/02/2025, 19:15 renderToPipeableStream – 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/renderToPipeableStream 9/18
20/02/2025, 19:15 renderToPipeableStream – React

The onShellReady callback fires when the entire shell has been rendered.
Usually, you’ll start streaming then:

const { pipe } = renderToPipeableStream(<App />, {


bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
}
});

By the time onShellReady fires, 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:

const { pipe } = renderToPipeableStream(<App />, {


bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});

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


errors to the console like above.

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

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. Override onShellError to send a
fallback HTML that doesn’t rely on server rendering as the last resort:

const { pipe } = renderToPipeableStream(<App />, {


bootstrapScripts: ['/main.js'],
onShellReady() {
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});

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

If there is an error while generating the shell, both onError and


onShellError will fire. Use onError for error reporting and use

onShellError 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.
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

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

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, you’ll get the onShellError callback which lets you set the error
status code. Otherwise, you know that the app may recover on the client, so
you can send “OK”.

const { pipe } = renderToPipeableStream(<App />, {


bootstrapScripts: ['/main.js'],
onShellReady() {
response.statusCode = 200;
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
console.error(error);
logServerCrashReport(error);
}

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

});

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 you will still get onShellReady instead of onShellError . 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:

let didError = false;

const { pipe } = renderToPipeableStream(<App />, {


bootstrapScripts: ['/main.js'],
onShellReady() {
response.statusCode = didError ? 500 : 200;
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});

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


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

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 your onError ,

onShellReady , and onShellError callbacks can do something different

depending on the error type:

let didError = false;


let caughtError = null;

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

const { pipe } = renderToPipeableStream(<App />, {


bootstrapScripts: ['/main.js'],
onShellReady() {
response.statusCode = getStatusCode();
response.setHeader('content-type', 'text/html');
pipe(response);
},
onShellError(error) {
response.statusCode = getStatusCode();
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onError(error) {
didError = true;
caughtError = error;
console.error(error);
logServerCrashReport(error);
}
});

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

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 using the onAllReady callback:

let didError = false;


let isCrawler = // ... depends on your bot detection strategy ...

const { pipe } = renderToPipeableStream(<App />, {


bootstrapScripts: ['/main.js'],
onShellReady() {
if (!isCrawler) {
response.statusCode = didError ? 500 : 200;
response.setHeader('content-type', 'text/html');
pipe(response);
}
},
onShellError(error) {
response.statusCode = 500;
response.setHeader('content-type', 'text/html');
response.send('<h1>Something went wrong</h1>');
},
onAllReady() {
if (isCrawler) {
response.statusCode = didError ? 500 : 200;
response.setHeader('content-type', 'text/html');
pipe(response);
}
},
https://react.dev/reference/react-dom/server/renderToPipeableStream 16/18
20/02/2025, 19:15 renderToPipeableStream – React

onError(error) {
didError = true;
console.error(error);
logServerCrashReport(error);
}
});

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:

const { pipe, abort } = renderToPipeableStream(<App />, {


// ...
});

setTimeout(() => {
abort();
}, 10000);

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

PREVIOUS

Server APIs

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

NEXT

renderToReadableStream

Copyright © Meta Platforms, Inc

uwu?

Learn React API Reference

Quick Start React APIs

Installation React DOM APIs

Describing the UI

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/renderToPipeableStream 18/18

You might also like