resume streams a pre-rendered React tree to a Readable Web Stream.
const stream = await resume(reactNode, postponedState, options?)Reference
resume(node, postponedState, 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, writable) {
  const postponed = await getPostponedState(request);
  const resumeStream = await resume(<App />, postponed);
  return resumeStream.pipeTo(writable)
}Parameters
- reactNode: The React node you called- prerenderwith. For example, a JSX element like- <App />. It is expected to represent the entire document, so the- Appcomponent should render the- <html>tag.
- postponedState: The opaque- postponeobject returned from a prerender API, loaded from wherever you stored it (e.g. redis, a file, or S3).
- optional options: An object with streaming options.- optional nonce: Anoncestring to allow scripts forscript-srcContent-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 resumesuccessfully produced a shell, that Promise will resolve to a Readable Web Stream. that can be piped to a Writable Web Stream..
- If an error happens in the shell, the Promise will reject with that error.
The returned stream has an additional property:
- allReady: A Promise that resolves when all rendering is complete. You can- await stream.allReadybefore 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
- resumedoes not accept options for- bootstrapScripts,- bootstrapScriptContent, or- bootstrapModules. Instead, you need to pass these options to the- prerendercall that generates the- postponedState. You can also inject bootstrap content into the writable stream manually.
- resumedoes not accept- identifierPrefixsince the prefix needs to be the same in both- prerenderand- resume.
- Since noncecannot be provided to prerender, you should only providenoncetoresumeif you’re not providing scripts to prerender.
- resumere-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.
Usage
Resuming a prerender
import { flushReadableStreamToFrame, getUser, Postponed, sleep, } from "./demo-helpers"; import { StrictMode, Suspense, use, useEffect } from "react"; import { prerender } from "react-dom/static"; import { resume } from "react-dom/server"; import { hydrateRoot } from "react-dom/client"; function Header() { return <header>Me and my descendants can be prerendered</header>; } const { promise: cookies, resolve: resolveCookies } = Promise.withResolvers(); function Main() { const { sessionID } = use(cookies); const user = getUser(sessionID); useEffect(() => { console.log("reached interactivity!"); }, []); return ( <main> Hello, {user.name}! <button onClick={() => console.log("hydrated!")}> Clicking me requires hydration. </button> </main> ); } function Shell({ children }) { // In a real app, this is where you would put your html and body. // We're just using tags here we can include in an existing body for demonstration purposes return ( <html> <body>{children}</body> </html> ); } function App() { return ( <Shell> <Suspense fallback="loading header"> <Header /> </Suspense> <Suspense fallback="loading main"> <Main /> </Suspense> </Shell> ); } async function main(frame) { // Layer 1 const controller = new AbortController(); const prerenderedApp = prerender(<App />, { signal: controller.signal, onError(error) { if (error instanceof Postponed) { } else { console.error(error); } }, }); // We're immediately aborting in a macrotask. // Any data fetching that's not available synchronously, or in a microtask, will not have finished. setTimeout(() => { controller.abort(new Postponed()); }); const { prelude, postponed } = await prerenderedApp; await flushReadableStreamToFrame(prelude, frame); // Layer 2 // Just waiting here for demonstration purposes. // In a real app, the prelude and postponed state would've been serialized in Layer 1 and Layer would deserialize them. // The prelude content could be flushed immediated as plain HTML while // React is continuing to render from where the prerender left off. await sleep(2000); // You would get the cookies from the incoming HTTP request resolveCookies({ sessionID: "abc" }); const stream = await resume(<App />, postponed); await flushReadableStreamToFrame(stream, frame); // Layer 3 // Just waiting here for demonstration purposes. await sleep(2000); hydrateRoot(frame.contentWindow.document, <App />); } main(document.getElementById("container"));
Further reading
Resuming behaves like renderToReadableStream. For more examples, check out the usage section of renderToReadableStream.
The usage section of prerender includes examples of how to use prerender specifically.