prerender renders a React tree to a static HTML string using aWeb Stream.
const{prelude,postponed} =awaitprerender(reactNode,options?)Note
This API depends onWeb Streams. For Node.js, useprerenderToNodeStream instead.
Reference
prerender(reactNode, options?)
Callprerender to render your app to static HTML.
import{prerender}from'react-dom/static';
asyncfunctionhandler(request,response){
const{prelude} =awaitprerender(<App/>,{
bootstrapScripts:['/main.js']
});
returnnewResponse(prelude,{
headers:{'content-type':'text/html'},
});
}On the client, callhydrateRoot to make the server-generated HTML interactive.
Parameters
reactNode: A React node you want to render to HTML. For example, a JSX node like<App />. It is expected to represent the entire document, so the App component should render the<html>tag.optional
options: An object with static generation options.- optional
bootstrapScriptContent: If specified, this string will be placed in an inline<script>tag. - optional
bootstrapScripts: An array of string URLs for the<script>tags to emit on the page. Use this to include the<script>that callshydrateRoot. Omit it if you don’t want to run React on the client at all. - optional
bootstrapModules: LikebootstrapScripts, but emits<script type="module">instead. - optional
identifierPrefix: A string prefix React uses for IDs generated byuseId. Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as passed tohydrateRoot. - optional
namespaceURI: A string with the rootnamespace 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
onError: A callback that fires whenever there is a server error, whetherrecoverable ornot. By default, this only callsconsole.error. If you override it tolog crash reports, make sure that you still callconsole.error. You can also use it toadjust the status code before the shell is emitted. - optional
progressiveChunkSize: The number of bytes in a chunk.Read more about the default heuristic. - optional
signal: Anabort signal that lets youabort prerendering and render the rest on the client.
- optional
Returns
prerender returns a Promise:
- If rendering the is successful, the Promise will resolve to an object containing:
prelude: aWeb Stream of HTML. You can use this stream to send a response in chunks, or you can read the entire stream into a string.postponed: a JSON-serializeable, opaque object that can be passed toresumeifprerenderdid not finish. Otherwisenullindicating that thepreludecontains all the content and no resume is necessary.
- If rendering fails, the Promise will be rejected.Use this to output a fallback shell.
Caveats
nonce is not an available option when prerendering. Nonces must be unique per request and if you use nonces to secure your application withCSP it would be inappropriate and insecure to include the nonce value in the prerender itself.
Note
When should I useprerender?
The staticprerender API is used for static server-side generation (SSG). UnlikerenderToString,prerender waits for all data to load before resolving. This makes it suitable for generating static HTML for a full page, including data that needs to be fetched using Suspense. To stream content as it loads, use a streaming server-side render (SSR) API likerenderToReadableStream.
prerender can be aborted and later either continued withresumeAndPrerender or resumed withresume to support partial pre-rendering.
Usage
Rendering a React tree to a stream of static HTML
Callprerender to render your React tree to static HTML into aReadable Web Stream::
import{prerender}from'react-dom/static';
asyncfunctionhandler(request){
const{prelude} =awaitprerender(<App />,{
bootstrapScripts:['/main.js']
});
returnnewResponse(prelude,{
headers:{'content-type':'text/html'},
});
}Along with theroot component, you need to provide a list ofbootstrap<script> paths. Your root component should returnthe entire document including the root<html> tag.
For example, it might look like this:
exportdefaultfunctionApp(){
return(
<html>
<head>
<metacharSet="utf-8"/>
<metaname="viewport"content="width=device-width, initial-scale=1"/>
<linkrel="stylesheet"href="/styles.css"></link>
<title>My app</title>
</head>
<body>
<Router/>
</body>
</html>
);
}React will inject thedoctype and yourbootstrap<script> tags into the resulting HTML stream:
<!DOCTYPE html>
<html>
<!-- ... HTML from your components ... -->
</html>
<scriptsrc="/main.js"async=""></script>On the client, your bootstrap script shouldhydrate the entiredocument with a call tohydrateRoot:
import{hydrateRoot}from'react-dom/client';
importAppfrom'./App.js';
hydrateRoot(document,<App />);This will attach event listeners to the static server-generated HTML and make it interactive.
Deep Dive
The final asset URLs (like JavaScript and CSS files) are often hashed after the build. For example, instead ofstyles.css you might end up withstyles.123456.css. Hashing static asset filenames guarantees that every distinct build of the same asset will have a different filename. This is useful because it lets you safely enable long-term caching for static assets: a file with a certain name would never change content.
However, if you don’t know the asset URLs until after the build, there’s no way for you to put them in the source code. For example, hardcoding"/styles.css" into JSX like earlier wouldn’t work. To keep them out of your source code, your root component can read the real filenames from a map passed as a prop:
exportdefaultfunctionApp({assetMap}){
return(
<html>
<head>
<title>My app</title>
<linkrel="stylesheet"href={assetMap['styles.css']}></link>
</head>
...
</html>
);
}On the server, render<App assetMap={assetMap} /> and pass yourassetMap with the asset URLs:
// You'd need to get this JSON from your build tooling, e.g. read it from the build output.
constassetMap ={
'styles.css':'/styles.123456.css',
'main.js':'/main.123456.js'
};
asyncfunctionhandler(request){
const{prelude} =awaitprerender(<AppassetMap={assetMap}/>,{
bootstrapScripts:[assetMap['/main.js']]
});
returnnewResponse(prelude,{
headers:{'content-type':'text/html'},
});
}Since your server is now rendering<App assetMap={assetMap} />, you need to render it withassetMap on the client too to avoid hydration errors. You can serialize and passassetMap to the client like this:
// You'd need to get this JSON from your build tooling.
constassetMap ={
'styles.css':'/styles.123456.css',
'main.js':'/main.123456.js'
};
asyncfunctionhandler(request){
const{prelude} =awaitprerender(<AppassetMap={assetMap}/>,{
// Careful: It's safe to stringify() this because this data isn't user-generated.
bootstrapScriptContent:`window.assetMap =${JSON.stringify(assetMap)};`,
bootstrapScripts:[assetMap['/main.js']],
});
returnnewResponse(prelude,{
headers:{'content-type':'text/html'},
});
}In the example above, thebootstrapScriptContent option adds an extra inline<script> tag that sets the globalwindow.assetMap variable on the client. This lets the client code read the sameassetMap:
import{hydrateRoot}from'react-dom/client';
importAppfrom'./App.js';
hydrateRoot(document,<AppassetMap={window.assetMap}/>);Both client and server renderApp with the sameassetMap prop, so there are no hydration errors.
Rendering a React tree to a string of static HTML
Callprerender to render your app to a static HTML string:
import{prerender}from'react-dom/static';
asyncfunctionrenderToString(){
const{prelude} =awaitprerender(<App/>,{
bootstrapScripts:['/main.js']
});
constreader =prelude.getReader();
letcontent ='';
while(true){
const{done,value} =awaitreader.read();
if(done){
returncontent;
}
content +=Buffer.from(value).toString('utf8');
}
}This will produce the initial non-interactive HTML output of your React components. On the client, you will need to callhydrateRoot tohydrate that server-generated HTML and make it interactive.
Waiting for all data to load
prerender waits for all data to load before finishing the static HTML generation and resolving. For example, consider a profile page that shows a cover, a sidebar with friends and photos, and a list of posts:
functionProfilePage(){
return(
<ProfileLayout>
<ProfileCover/>
<Sidebar>
<Friends/>
<Photos/>
</Sidebar>
<Suspensefallback={<PostsGlimmer/>}>
<Posts/>
</Suspense>
</ProfileLayout>
);
}Imagine that<Posts /> needs to load some data, which takes some time. Ideally, you’d want wait for the posts to finish so it’s included in the HTML. To do this, you can use Suspense to suspend on the data, andprerender will wait for the suspended content to finish before resolving to the static HTML.
Note
Only Suspense-enabled data sources will activate the Suspense component. They include:
- Data fetching with Suspense-enabled frameworks likeRelay andNext.js
- Lazy-loading component code with
lazy - Reading the value of a Promise with
use
Suspensedoes not detect when data is fetched inside an Effect or event handler.
The exact way you would load data in thePosts 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.
Aborting prerendering
You can force the prerender to “give up” after a timeout:
asyncfunctionrenderToString(){
constcontroller =newAbortController();
setTimeout(()=>{
controller.abort()
},10000);
try{
// the prelude will contain all the HTML that was prerendered
// before the controller aborted.
const{prelude} =awaitprerender(<App/>,{
signal:controller.signal,
});
//...Any Suspense boundaries with incomplete children will be included in the prelude in the fallback state.
This can be used for partial prerendering together withresume orresumeAndPrerender.
Troubleshooting
My stream doesn’t start until the entire app is rendered
Theprerender response waits for the entire app to finish rendering, including waiting for all Suspense boundaries to resolve, before resolving. It is designed for static site generation (SSG) ahead of time and does not support streaming more content as it loads.
To stream content as it loads, use a streaming server render API likerenderToReadableStream.