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:

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:

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 new renderToPipeableStream 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.

This will be the recommended API going forward. It has all the new features:

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:

The new recommended API for them that mirrors renderToPipeableStream has not been added yet.