Skip to content

Commit aba6b86

Browse files
Docs for partial-prerendering APIs (#7869)
* Init PPR docs * Overhaul * Apply suggestions from code review --------- Co-authored-by: Sebastian Sebbie Silbermann <[email protected]>
1 parent 9fddeca commit aba6b86

File tree

9 files changed

+585
-13
lines changed

9 files changed

+585
-13
lines changed

src/content/reference/react-dom/server/index.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,27 @@ The `react-dom/server` APIs let you server-side render React components to HTML.
1010

1111
---
1212

13-
## Server APIs for Node.js Streams {/*server-apis-for-nodejs-streams*/}
13+
## Server APIs for Web Streams {/*server-apis-for-web-streams*/}
1414

15-
These methods are only available in the environments with [Node.js Streams:](https://nodejs.org/api/stream.html)
15+
These methods are only available in the environments with [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API), which includes browsers, Deno, and some modern edge runtimes:
1616

17-
* [`renderToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) renders a React tree to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html)
17+
* [`renderToReadableStream`](/reference/react-dom/server/renderToReadableStream) renders a React tree to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
18+
* [`resume`](/reference/react-dom/server/renderToPipeableStream) resumes [`prerender`](/reference/react-dom/static/prerender) to a [Readable Web Stream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
19+
20+
21+
<Note>
1822

23+
Node.js also includes these methods for compatibility, but they are not recommended due to worse performance. Use the [dedicated Node.js APIs](#server-apis-for-nodejs-streams) instead.
24+
25+
</Note>
1926
---
2027

21-
## Server APIs for Web Streams {/*server-apis-for-web-streams*/}
28+
## Server APIs for Node.js Streams {/*server-apis-for-nodejs-streams*/}
2229

23-
These methods are only available in the environments with [Web Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API), which includes browsers, Deno, and some modern edge runtimes:
30+
These methods are only available in the environments with [Node.js Streams:](https://nodejs.org/api/stream.html)
2431

25-
* [`renderToReadableStream`](/reference/react-dom/server/renderToReadableStream) renders a React tree to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
32+
* [`renderToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) renders a React tree to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html)
33+
* [`resumeToPipeableStream`](/reference/react-dom/server/renderToPipeableStream) resumes [`prerenderToNodeStream`](/reference/react-dom/static/prerenderToNodeStream) to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html)
2634

2735
---
2836

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
---
2+
title: resume
3+
canary: true
4+
---
5+
6+
<Canary>
7+
8+
**The `resume` API is currently only available in React’s Canary and Experimental channels.**
9+
10+
[Learn more about React’s release channels here.](/community/versioning-policy#all-release-channels)
11+
12+
</Canary>
13+
14+
<Intro>
15+
16+
`resume` streams a pre-rendered React tree to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
17+
18+
```js
19+
const stream = await resume(reactNode, postponedState, options?)
20+
```
21+
22+
</Intro>
23+
24+
<InlineToc />
25+
26+
<Note>
27+
28+
This API depends on [Web Streams.](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) For Node.js, use [`resumeToNodeStream`](/reference/react-dom/server/renderToPipeableStream) instead.
29+
30+
</Note>
31+
32+
---
33+
34+
## Reference {/*reference*/}
35+
36+
### `resume(node, postponedState, options?)` {/*resume*/}
37+
38+
Call `resume` to resume rendering a pre-rendered React tree as HTML into a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
39+
40+
```js
41+
import { resume } from 'react-dom/server';
42+
import {getPostponedState} from './storage';
43+
44+
async function handler(request, writable) {
45+
const postponed = await getPostponedState(request);
46+
const resumeStream = await resume(<App />, postponed);
47+
return resumeStream.pipeTo(writable)
48+
}
49+
```
50+
51+
[See more examples below.](#usage)
52+
53+
#### Parameters {/*parameters*/}
54+
55+
* `reactNode`: The React node you called `prerender` with. For example, a JSX element like `<App />`. It is expected to represent the entire document, so the `App` component should render the `<html>` tag.
56+
* `postponedState`: The opaque `postpone` object returned from a [prerender API](/reference/react-dom/static/index), loaded from wherever you stored it (e.g. redis, a file, or S3).
57+
* **optional** `options`: An object with streaming options.
58+
* **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src).
59+
* **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client.
60+
* **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-outside-the-shell) or [not.](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](/reference/react-dom/server/renderToReadableStream#logging-crashes-on-the-server) make sure that you still call `console.error`.
61+
62+
63+
#### Returns {/*returns*/}
64+
65+
`resume` returns a Promise:
66+
67+
- If `resume` successfully produced a [shell](/reference/react-dom/server/renderToReadableStream#specifying-what-goes-into-the-shell), that Promise will resolve to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) that can be piped to a [Writable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream).
68+
- If an error happens in the shell, the Promise will reject with that error.
69+
70+
The returned stream has an additional property:
71+
72+
* `allReady`: A Promise that resolves when all rendering is complete. You can `await stream.allReady` before returning a response [for crawlers and static generation.](/reference/react-dom/server/renderToReadableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation) If you do that, you won't get any progressive loading. The stream will contain the final HTML.
73+
74+
#### Caveats {/*caveats*/}
75+
76+
- `resume` does not accept options for `bootstrapScripts`, `bootstrapScriptContent`, or `bootstrapModules`. Instead, you need to pass these options to the `prerender` call that generates the `postponedState`. You can also inject bootstrap content into the writable stream manually.
77+
- `resume` does not accept `identifierPrefix` since the prefix needs to be the same in both `prerender` and `resume`.
78+
- Since `nonce` cannot be provided to prerender, you should only provide `nonce` to `resume` if you're not providing scripts to prerender.
79+
- `resume` re-renders from the root until it finds a component that was not fully pre-rendered. Only fully prerendered Components (the Component and its children finished prerendering) are skipped entirely.
80+
81+
## Usage {/*usage*/}
82+
83+
### Resuming a prerender {/*resuming-a-prerender*/}
84+
85+
<Sandpack>
86+
87+
```js src/App.js hidden
88+
```
89+
90+
```json package.json hidden
91+
{
92+
"dependencies": {
93+
"react": "experimental",
94+
"react-dom": "experimental",
95+
"react-scripts": "latest"
96+
},
97+
"scripts": {
98+
"start": "react-scripts start",
99+
"build": "react-scripts build",
100+
"test": "react-scripts test --env=jsdom",
101+
"eject": "react-scripts eject"
102+
}
103+
}
104+
```
105+
106+
```html public/index.html
107+
<!DOCTYPE html>
108+
<html lang="en">
109+
<head>
110+
<meta charset="UTF-8">
111+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
112+
<title>Document</title>
113+
</head>
114+
<body>
115+
<iframe id="container"></iframe>
116+
</body>
117+
</html>
118+
```
119+
120+
```js src/index.js
121+
import {
122+
flushReadableStreamToFrame,
123+
getUser,
124+
Postponed,
125+
sleep,
126+
} from "./demo-helpers";
127+
import { StrictMode, Suspense, use, useEffect } from "react";
128+
import { prerender } from "react-dom/static";
129+
import { resume } from "react-dom/server";
130+
import { hydrateRoot } from "react-dom/client";
131+
132+
function Header() {
133+
return <header>Me and my descendants can be prerendered</header>;
134+
}
135+
136+
const { promise: cookies, resolve: resolveCookies } = Promise.withResolvers();
137+
138+
function Main() {
139+
const { sessionID } = use(cookies);
140+
const user = getUser(sessionID);
141+
142+
useEffect(() => {
143+
console.log("reached interactivity!");
144+
}, []);
145+
146+
return (
147+
<main>
148+
Hello, {user.name}!
149+
<button onClick={() => console.log("hydrated!")}>
150+
Clicking me requires hydration.
151+
</button>
152+
</main>
153+
);
154+
}
155+
156+
function Shell({ children }) {
157+
// In a real app, this is where you would put your html and body.
158+
// We're just using tags here we can include in an existing body for demonstration purposes
159+
return (
160+
<html>
161+
<body>{children}</body>
162+
</html>
163+
);
164+
}
165+
166+
function App() {
167+
return (
168+
<Shell>
169+
<Suspense fallback="loading header">
170+
<Header />
171+
</Suspense>
172+
<Suspense fallback="loading main">
173+
<Main />
174+
</Suspense>
175+
</Shell>
176+
);
177+
}
178+
179+
async function main(frame) {
180+
// Layer 1
181+
const controller = new AbortController();
182+
const prerenderedApp = prerender(<App />, {
183+
signal: controller.signal,
184+
onError(error) {
185+
if (error instanceof Postponed) {
186+
} else {
187+
console.error(error);
188+
}
189+
},
190+
});
191+
// We're immediately aborting in a macrotask.
192+
// Any data fetching that's not available synchronously, or in a microtask, will not have finished.
193+
setTimeout(() => {
194+
controller.abort(new Postponed());
195+
});
196+
197+
const { prelude, postponed } = await prerenderedApp;
198+
await flushReadableStreamToFrame(prelude, frame);
199+
200+
// Layer 2
201+
// Just waiting here for demonstration purposes.
202+
// In a real app, the prelude and postponed state would've been serialized in Layer 1 and Layer would deserialize them.
203+
// The prelude content could be flushed immediated as plain HTML while
204+
// React is continuing to render from where the prerender left off.
205+
await sleep(2000);
206+
207+
// You would get the cookies from the incoming HTTP request
208+
resolveCookies({ sessionID: "abc" });
209+
210+
const stream = await resume(<App />, postponed);
211+
212+
await flushReadableStreamToFrame(stream, frame);
213+
214+
// Layer 3
215+
// Just waiting here for demonstration purposes.
216+
await sleep(2000);
217+
218+
hydrateRoot(frame.contentWindow.document, <App />);
219+
}
220+
221+
main(document.getElementById("container"));
222+
223+
```
224+
225+
```js src/demo-helpers.js
226+
export async function flushReadableStreamToFrame(readable, frame) {
227+
const document = frame.contentWindow.document;
228+
const decoder = new TextDecoder();
229+
for await (const chunk of readable) {
230+
const partialHTML = decoder.decode(chunk);
231+
document.write(partialHTML);
232+
}
233+
}
234+
235+
// This doesn't need to be an error.
236+
// You can use any other means to check if an error during prerender was
237+
// from an intentional abort or a real error.
238+
export class Postponed extends Error {}
239+
240+
// We're just hardcoding a session here.
241+
export function getUser(sessionID) {
242+
return {
243+
name: "Alice",
244+
};
245+
}
246+
247+
export function sleep(timeoutMS) {
248+
return new Promise((resolve) => {
249+
setTimeout(() => {
250+
resolve();
251+
}, timeoutMS);
252+
});
253+
}
254+
```
255+
256+
</Sandpack>
257+
258+
### Further reading {/*further-reading*/}
259+
260+
Resuming behaves like `renderToReadableStream`. For more examples, check out the [usage section of `renderToReadableStream`](/reference/react-dom/server/renderToReadableStream#usage).
261+
The [usage section of `prerender`](/reference/react-dom/static/prerender#usage) includes examples of how to use `prerender` specifically.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
title: resumeToPipeableStream
3+
canary: true
4+
---
5+
6+
<Canary>
7+
8+
**The `resumeToPipeableStream` API is currently only available in React’s Canary and Experimental channels.**
9+
10+
[Learn more about React’s release channels here.](/community/versioning-policy#all-release-channels)
11+
12+
</Canary>
13+
14+
<Intro>
15+
16+
`resumeToPipeableStream` streams a pre-rendered React tree to a pipeable [Node.js Stream.](https://nodejs.org/api/stream.html)
17+
18+
```js
19+
const {pipe, abort} = await resumeToPipeableStream(reactNode, postponedState, options?)
20+
```
21+
22+
</Intro>
23+
24+
<InlineToc />
25+
26+
<Note>
27+
28+
This API is specific to Node.js. Environments with [Web Streams,](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) like Deno and modern edge runtimes, should use [`resume`](/reference/react-dom/server/renderToReadableStream) instead.
29+
30+
</Note>
31+
32+
---
33+
34+
## Reference {/*reference*/}
35+
36+
### `resumeToPipeableStream(node, postponed, options?)` {/*resume-to-pipeable-stream*/}
37+
38+
Call `resume` to resume rendering a pre-rendered React tree as HTML into a [Node.js Stream.](https://nodejs.org/api/stream.html#writable-streams)
39+
40+
```js
41+
import { resume } from 'react-dom/server';
42+
import {getPostponedState} from './storage';
43+
44+
async function handler(request, response) {
45+
const postponed = await getPostponedState(request);
46+
const {pipe} = resumeToPipeableStream(<App />, postponed, {
47+
onShellReady: () => {
48+
pipe(response);
49+
}
50+
});
51+
}
52+
```
53+
54+
[See more examples below.](#usage)
55+
56+
#### Parameters {/*parameters*/}
57+
58+
* `reactNode`: The React node you called `prerender` with. For example, a JSX element like `<App />`. It is expected to represent the entire document, so the `App` component should render the `<html>` tag.
59+
* `postponedState`: The opaque `postpone` object returned from a [prerender API](/reference/react-dom/static/index), loaded from wherever you stored it (e.g. redis, a file, or S3).
60+
* **optional** `options`: An object with streaming options.
61+
* **optional** `nonce`: A [`nonce`](http://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#nonce) string to allow scripts for [`script-src` Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src).
62+
* **optional** `signal`: An [abort signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client.
63+
* **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-outside-the-shell) or [not.](/reference/react-dom/server/renderToReadableStream#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](/reference/react-dom/server/renderToReadableStream#logging-crashes-on-the-server) make sure that you still call `console.error`.
64+
* **optional** `onShellReady`: A callback that fires right after the [shell](#specifying-what-goes-into-the-shell) has finished. You can call `pipe` here to start streaming. React will [stream the additional content](#streaming-more-content-as-it-loads) after the shell along with the inline `<script>` tags that replace the HTML loading fallbacks with the content.
65+
* **optional** `onShellError`: A callback that fires if there was an error rendering the 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](#recovering-from-errors-inside-the-shell) or use the prelude.
66+
67+
68+
#### Returns {/*returns*/}
69+
70+
`resume` returns an object with two methods:
71+
72+
* `pipe` outputs the HTML into the provided [Writable Node.js Stream.](https://nodejs.org/api/stream.html#writable-streams) Call `pipe` in `onShellReady` if you want to enable streaming, or in `onAllReady` for crawlers and static generation.
73+
* `abort` lets you [abort server rendering](#aborting-server-rendering) and render the rest on the client.
74+
75+
#### Caveats {/*caveats*/}
76+
77+
- `resumeToPipeableStream` does not accept options for `bootstrapScripts`, `bootstrapScriptContent`, or `bootstrapModules`. Instead, you need to pass these options to the `prerender` call that generates the `postponedState`. You can also inject bootstrap content into the writable stream manually.
78+
- `resumeToPipeableStream` does not accept `identifierPrefix` since the prefix needs to be the same in both `prerender` and `resumeToPipeableStream`.
79+
- Since `nonce` cannot be provided to prerender, you should only provide `nonce` to `resumeToPipeableStream` if you're not providing scripts to prerender.
80+
- `resumeToPipeableStream` re-renders from the root until it finds a component that was not fully pre-rendered. Only fully prerendered Components (the Component and its children finished prerendering) are skipped entirely.
81+
82+
## Usage {/*usage*/}
83+
84+
### Further reading {/*further-reading*/}
85+
86+
Resuming behaves like `renderToReadableStream`. For more examples, check out the [usage section of `renderToReadableStream`](/reference/react-dom/server/renderToReadableStream#usage).
87+
The [usage section of `prerender`](/reference/react-dom/static/prerender#usage) includes examples of how to use `prerenderToNodeStream` specifically.

0 commit comments

Comments
 (0)