|
| 1 | +--- |
| 2 | +title: resume |
| 3 | +canary: true |
| 4 | +--- |
| 5 | + |
| 6 | +<Intro> |
| 7 | + |
| 8 | +`resume` streams a pre-rendered React tree to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) |
| 9 | + |
| 10 | +```js |
| 11 | +const stream = await resume(reactNode, postponedState, options?) |
| 12 | +``` |
| 13 | +
|
| 14 | +</Intro> |
| 15 | +
|
| 16 | +<InlineToc /> |
| 17 | +
|
| 18 | +<Note> |
| 19 | +
|
| 20 | +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. |
| 21 | +
|
| 22 | +</Note> |
| 23 | +
|
| 24 | +--- |
| 25 | +
|
| 26 | +## Reference {/*reference*/} |
| 27 | +
|
| 28 | +### `resume(node, postponed, options?)` {/*resume*/} |
| 29 | +
|
| 30 | +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) |
| 31 | +
|
| 32 | +```js |
| 33 | +import { resume } from 'react-dom/server'; |
| 34 | +import {getPostponedState} from 'storage'; |
| 35 | + |
| 36 | +async function handler(request) { |
| 37 | + const postponed = await getPostponedState(request); |
| 38 | + const stream = await resume(<App />, postponed, { |
| 39 | + bootstrapScripts: ['/main.js'] |
| 40 | + }); |
| 41 | + return new Response(stream, { |
| 42 | + headers: { 'content-type': 'text/html' }, |
| 43 | + }); |
| 44 | +} |
| 45 | +``` |
| 46 | +
|
| 47 | +TODO: when do you call hydrateRoot? In the shell or when you resume? |
| 48 | +
|
| 49 | +[See more examples below.](#usage) |
| 50 | +
|
| 51 | +#### Parameters {/*parameters*/} |
| 52 | +
|
| 53 | +* `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. |
| 54 | +* `postponedState`: The opaque `postpone` object returned from `prerender`, loaded from wherever you stored it (e.g. redis, a file, or S3). |
| 55 | +* **optional** `options`: An object with streaming options. |
| 56 | + * **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). |
| 57 | + * **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. |
| 58 | + * **optional** `onError`: A callback that fires whenever there is a server error, whether [recoverable](#recovering-from-errors-outside-the-shell) or [not.](#recovering-from-errors-inside-the-shell) By default, this only calls `console.error`. If you override it to [log crash reports,](#logging-crashes-on-the-server) make sure that you still call `console.error`. |
| 59 | +
|
| 60 | +
|
| 61 | +#### Returns {/*returns*/} |
| 62 | +
|
| 63 | +`resume` returns a Promise: |
| 64 | +
|
| 65 | +- If `prerender` successfully produced a [shell](#specifying-what-goes-into-the-shell) is successful, that Promise will resolve to a [Readable Web Stream.](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) whether replaying the shell errors or not. |
| 66 | +- If `prerender` failed to produce a [shell](#specifying-what-goes-into-the-shell), and `resume` errors, the Promise will be rejected. TODO: Example? |
| 67 | +
|
| 68 | +The returned stream has an additional property: |
| 69 | +
|
| 70 | +* `allReady`: A Promise that resolves when all rendering is complete. You can `await stream.allReady` before returning a response [for crawlers and static generation.](#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. |
| 71 | +
|
| 72 | +#### Caveats {/*caveats*/} |
| 73 | +- `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`. These may be injected either during pre-render or resume. |
| 74 | +- `resume` does not accept `identifierPrefix` since the prefix needs to be the same in both `prerender` and `resume`. |
| 75 | +- Since `nonce` cannot be provided to prerender, you should only provide `nonce` to `resume` if you're not providing scripts to prerender. |
| 76 | +- `resume` re-renders from the root until it finds a component that was not fully pre-rendered, and skips fully pre-rendered components. |
| 77 | +--- |
| 78 | +
|
| 79 | +## Usage {/*usage*/} |
| 80 | +
|
| 81 | +### Resuming a prerender to a Readable Web Stream {/*resuming-a-prerender-to-a-readable-web-stream*/} |
| 82 | +
|
| 83 | +TODO |
| 84 | +
|
| 85 | +--- |
| 86 | +
|
| 87 | +### Logging crashes on the server {/*logging-crashes-on-the-server*/} |
| 88 | +
|
| 89 | +By default, all errors on the server are logged to console. You can override this behavior to log crash reports: |
| 90 | +
|
| 91 | +```js {9-10} |
| 92 | +import { resume } from 'react-dom/server'; |
| 93 | +import { getPostponedState } from 'storage'; |
| 94 | +import { logServerCrashReport } from 'logging'; |
| 95 | + |
| 96 | +async function handler(request) { |
| 97 | + const postponed = await getPostponedState(request); |
| 98 | + const stream = await resume(<App />, postponed, { |
| 99 | + onError(error) { |
| 100 | + console.error(error); |
| 101 | + logServerCrashReport(error); |
| 102 | + } |
| 103 | + }); |
| 104 | + return new Response(stream, { |
| 105 | + headers: { 'content-type': 'text/html' }, |
| 106 | + }); |
| 107 | +} |
| 108 | +``` |
| 109 | +
|
| 110 | +If you provide a custom `onError` implementation, don't forget to also log errors to the console like above. |
| 111 | +
|
| 112 | +--- |
| 113 | +
|
| 114 | +### Recovering from errors replaying the shell {/*recovering-from-errors-inside-the-shell*/} |
| 115 | +
|
| 116 | +TODO: this is for when the shell completed. |
| 117 | +
|
| 118 | +In this example, prerender successfully rendered a shell containing `ProfileLayout`, `ProfileCover`, and `PostsGlimmer`: |
| 119 | +
|
| 120 | +```js {3-5,7-8} |
| 121 | +function ProfilePage() { |
| 122 | + return ( |
| 123 | + <ProfileLayout> |
| 124 | + <ProfileCover /> |
| 125 | + <Suspense fallback={<PostsGlimmer />}> |
| 126 | + <Posts /> |
| 127 | + </Suspense> |
| 128 | + </ProfileLayout> |
| 129 | + ); |
| 130 | +} |
| 131 | +``` |
| 132 | +
|
| 133 | +If an error occurs while replaying those components, React won't have any meaningful HTML to send to the client. TODO: how to recover from this, since the promise is resolved. I think it will just encode an error in the stream and trigger an error boundary? |
| 134 | +
|
| 135 | +```js {2,13-18} |
| 136 | +// TODO |
| 137 | +``` |
| 138 | +
|
| 139 | +If there is an error while replaying the shell, it will be logged to `onError`. |
| 140 | +
|
| 141 | +### Recovering from errors re-creating the shell {/*recovering-from-errors-re-creating-the-shell*/} |
| 142 | +
|
| 143 | +TODO: this is for when the shell errors, and re-creating the shell fails. |
| 144 | +
|
| 145 | +--- |
| 146 | +
|
| 147 | +### Recovering from errors outside the shell {/*recovering-from-errors-outside-the-shell*/} |
| 148 | +
|
| 149 | +TODO: confirm this section is correct. |
| 150 | +
|
| 151 | +In this example, the `<Posts />` component is wrapped in `<Suspense>` so it is *not* a part of the shell: |
| 152 | +
|
| 153 | +```js {6} |
| 154 | +function ProfilePage() { |
| 155 | + return ( |
| 156 | + <ProfileLayout> |
| 157 | + <ProfileCover /> |
| 158 | + <Suspense fallback={<PostsGlimmer />}> |
| 159 | + <Posts /> |
| 160 | + </Suspense> |
| 161 | + </ProfileLayout> |
| 162 | + ); |
| 163 | +} |
| 164 | +``` |
| 165 | +
|
| 166 | +If an error happens in the `Posts` component or somewhere inside it, React will [try to recover from it:](/reference/react/Suspense#providing-a-fallback-for-server-errors-and-client-only-content) |
| 167 | +
|
| 168 | +1. It will emit the loading fallback for the closest `<Suspense>` boundary (`PostsGlimmer`) into the HTML. |
| 169 | +2. It will "give up" on trying to render the `Posts` content on the server anymore. |
| 170 | +3. When the JavaScript code loads on the client, React will *retry* rendering `Posts` on the client. |
| 171 | +
|
| 172 | +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](/reference/react/Component#static-getderivedstatefromerror) 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. |
| 173 | +
|
| 174 | +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`](/reference/react-dom/client/hydrateRoot#hydrateroot) callbacks will fire so that you can get notified about the error. |
| 175 | +
|
| 176 | +--- |
| 177 | +
|
| 178 | +### Setting the status code {/*setting-the-status-code*/} |
| 179 | +
|
| 180 | +TODO: you can't set the status code in resume, unless you're calling prerender in the same request. If so, set the status code between `prerender` and `resume`. |
| 181 | +
|
| 182 | +--- |
| 183 | +
|
| 184 | +### Handling different errors in different ways {/*handling-different-errors-in-different-ways*/} |
| 185 | +
|
| 186 | +TODO: update this example. |
| 187 | +
|
| 188 | +You can [create your own `Error` subclasses](https://javascript.info/custom-errors) and use the [`instanceof`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/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: |
| 189 | +
|
| 190 | +```js {2-3,5-15,22,28,33} |
| 191 | +async function handler(request) { |
| 192 | + let didError = false; |
| 193 | + let caughtError = null; |
| 194 | + |
| 195 | + function getStatusCode() { |
| 196 | + if (didError) { |
| 197 | + if (caughtError instanceof NotFoundError) { |
| 198 | + return 404; |
| 199 | + } else { |
| 200 | + return 500; |
| 201 | + } |
| 202 | + } else { |
| 203 | + return 200; |
| 204 | + } |
| 205 | + } |
| 206 | + |
| 207 | + try { |
| 208 | + const stream = await renderToReadableStream(<App />, { |
| 209 | + bootstrapScripts: ['/main.js'], |
| 210 | + onError(error) { |
| 211 | + didError = true; |
| 212 | + caughtError = error; |
| 213 | + console.error(error); |
| 214 | + logServerCrashReport(error); |
| 215 | + } |
| 216 | + }); |
| 217 | + return new Response(stream, { |
| 218 | + status: getStatusCode(), |
| 219 | + headers: { 'content-type': 'text/html' }, |
| 220 | + }); |
| 221 | + } catch (error) { |
| 222 | + return new Response('<h1>Something went wrong</h1>', { |
| 223 | + status: getStatusCode(), |
| 224 | + headers: { 'content-type': 'text/html' }, |
| 225 | + }); |
| 226 | + } |
| 227 | +} |
| 228 | +``` |
| 229 | +
|
| 230 | +--- |
| 231 | +
|
| 232 | +### Waiting for all content to load for crawlers and static generation {/*waiting-for-all-content-to-load-for-crawlers-and-static-generation*/} |
| 233 | +
|
| 234 | +TODO: this doesn't make sense for `resume` right? |
| 235 | +
|
| 236 | +--- |
| 237 | +
|
| 238 | +### Aborting server rendering {/*aborting-server-rendering*/} |
| 239 | +
|
| 240 | +TODO |
0 commit comments