Upgrading to React 18 on the server · reactwg/react-18 · Discussion #22 (original) (raw)
Overview
Upgrading to React 18 on the server has the following steps:
- Upgrade to the latest React 18 release
- Optionally, switch from
renderToString
torenderToPipeableStream
to unlock the new features
React 18 includes architectural improvements to React server-side rendering (SSR) performance described in New SSR Suspense Architecture. These improvements are substantial and are the culmination of several years of work.
API Changes
Previously, React did not support Suspense on the server at all. This is changing in React 18, but there are different levels of support depending on which API you use:
renderToString
: Keeps working (with limited Suspense support).renderToNodeStream
: Deprecated (with full Suspense support, but without streaming).renderToPipeableStream
: New and recommended (with full Suspense support and streaming).
Existing API: renderToString(React.Node): string
This API continues to work. It doesn’t support new features so we recommend switching to renderToPipeableStream
. However, it’s not deprecated, so you can keep using renderToString
.
In React 18, we’re adding very limited <Suspense>
support to renderToString
. Previously, trying to use <Suspense>
with it threw an error. Starting in React 18, if you suspend during renderToString
, we will mark the nearest Suspense boundary as "client-rendered" and immediately emit the fallback HTML. Then, we will retry rendering its content on the client after the JS has loaded. Concretely, this means that if you wrap your app in a top-level <Suspense>
boundary and you suspend during rendering, your app effectively opts out of server rendering.
This change shouldn't affect existing apps because suspending on the server previously did not work at all. However, if you tried to render <Suspense>
on the client only by conditionally suspending, you may now get mismatches leading to content being removed from the DOM. Conditionally rendering different content on the server and the client’s first render is not (and has not been) supported in React in general.
Note: This behavior isn’t very useful, but it’s the best we can do because
renderToString
is synchronous. It can't “wait” for anything. This is why we recommend the newrenderToPipeableStream
instead.
Deprecated API: renderToNodeStream(React.Node): Readable
We will be deprecating renderToNodeStream
completely in React 18—using it will warn. This was the first streaming API that we added, but it was very underpowered (it could not wait for data). It's also not commonly used. It will work in 18, including the new Suspense features described below, but it will buffer the entire content until the end of the stream. In other words, it will no longer do streaming. This makes its purpose confusing, which is why we’re deprecating it.
We are replacing it with renderToPipeableStream
.
Recommended API: renderToPipeableStream(React.Node, Options): Controls
This will be the recommended API going forward. It has all the new features:
- Full built-in support for
<Suspense>
(which integrates with data fetching) - Code splitting with
lazy
without flashes of "disappearing" content - Streaming of HTML with "delayed" content blocks "popping in" later
In the latest Alpha versions, you can get it from:
import { renderToPipeableStream } from 'react-dom/server';
Unlike renderToString()
, it involves more wiring. We've prepared an example demo of how you could change your code from using renderToString() to renderToPipeableStream(). We will provide more detailed documentation later, but the demo should get you started.
To learn more about what this API unlocks, read New SSR Suspense Architecture.
This API is not yet integrated with data fetching. The general Suspense mechanism should work, but we don't have a recommendation yet for how to transfer data from the server to the client to prepopulate the cache. We expect to provide more guidance on this in the future.
There are also some questions you'll want to experiment with. For example, how to deal with the <title>
tags. Since this is a true streaming API, you might not have an "ideal" title by the time you start streaming. There are various solutions you could try, and we're curious to hear if you find something that works well for you as you test this API in your app and experiment with different approaches.
Other APIs
There are a few other APIs related to generating static markup. Their behavior mirrors the main APIs:
renderToStaticMarkup
: Keeps working (with limited Suspense support).renderToStaticNodeStream
: Deprecated (with full Suspense support, but without streaming).
The new recommended API for them that mirrors renderToPipeableStream
has not been added yet.