resume
resume
streams a pre-rendered React tree to a Readable Web Stream.
const stream = await resume(reactNode, postponedState, options?)
- Reference
- Usage
- Resuming a prerender to a Readable Web Stream
- Logging crashes on the server
- Recovering from errors replaying the shell
- Recovering from errors re-creating 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
Reference
resume(node, postponed, options?)
Call resume
to resume rendering a pre-rendered React tree as HTML into a Readable Web Stream.
import { resume } from 'react-dom/server';
import {getPostponedState} from 'storage';
async function handler(request) {
const postponed = await getPostponedState(request);
const stream = await resume(<App />, postponed, {
bootstrapScripts: ['/main.js']
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}
TODO: when do you call hydrateRoot? In the shell or when you resume?
Parameters
reactNode
: The React node you calledprerender
with. For example, a JSX element like<App />
. It is expected to represent the entire document, so theApp
component should render the<html>
tag.postponedState
: The opaquepostpone
object returned fromprerender
, loaded from wherever you stored it (e.g. redis, a file, or S3).- optional
options
: An object with streaming options.- optional
nonce
: Anonce
string to allow scripts forscript-src
Content-Security-Policy. - optional
signal
: An abort signal that lets you abort server rendering and render the rest on the client. - optional
onError
: A callback that fires whenever there is a server error, whether recoverable or not. By default, this only callsconsole.error
. If you override it to log crash reports, make sure that you still callconsole.error
.
- optional
Returns
resume
returns a Promise:
- If
prerender
successfully produced a shell is successful, that Promise will resolve to a Readable Web Stream. whether replaying the shell errors or not. - If
prerender
failed to produce a shell, andresume
errors, the Promise will be rejected. TODO: Example?
The returned stream has an additional property:
allReady
: A Promise that resolves when all rendering is complete. You canawait 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.
Caveats
resume
does not accept options forbootstrapScripts
,bootstrapScriptContent
, orbootstrapModules
. Instead, you need to pass these options to theprerender
call that generates thepostponedState
. These may be injected either during pre-render or resume.resume
does not acceptidentifierPrefix
since the prefix needs to be the same in bothprerender
andresume
.- Since
nonce
cannot be provided to prerender, you should only providenonce
toresume
if you’re not providing scripts to prerender. resume
re-renders from the root until it finds a component that was not fully pre-rendered, and skips fully pre-rendered components.
Usage
Resuming a prerender to a Readable Web Stream
TODO
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:
import { resume } from 'react-dom/server';
import { getPostponedState } from 'storage';
import { logServerCrashReport } from 'logging';
async function handler(request) {
const postponed = await getPostponedState(request);
const stream = await resume(<App />, postponed, {
onError(error) {
console.error(error);
logServerCrashReport(error);
}
});
return new Response(stream, {
headers: { 'content-type': 'text/html' },
});
}
If you provide a custom onError
implementation, don’t forget to also log errors to the console like above.
Recovering from errors replaying the shell
TODO: this is for when the shell completed.
In this example, prerender successfully rendered a shell containing ProfileLayout
, ProfileCover
, and PostsGlimmer
:
function ProfilePage() {
return (
<ProfileLayout>
<ProfileCover />
<Suspense fallback={<PostsGlimmer />}>
<Posts />
</Suspense>
</ProfileLayout>
);
}
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?
// TODO
If there is an error while replaying the shell, it will be logged to onError
.
Recovering from errors re-creating the shell
TODO: this is for when the shell errors, and re-creating the shell fails.
Recovering from errors outside the shell
TODO: confirm this section is correct.
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:
- It will emit the loading fallback for the closest
<Suspense>
boundary (PostsGlimmer
) into the HTML. - It will “give up” on trying to render the
Posts
content on the server anymore. - 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
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
.
Handling different errors in different ways
TODO: update this example.
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) {
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' },
});
}
}
Waiting for all content to load for crawlers and static generation
TODO: this doesn’t make sense for resume
right?
Aborting server rendering
TODO