Learn React 18 in 5 minutes
React 18 is the next major version of React. An alpha version was released in June 2021 and a beta version in November. Next.js 12 also added support for React 18 as an experimental feature. The footsteps of React 18 are getting closer to us, and React 18 is finally in the sight of everyone who is not an early adopter.
In particular, React 18 adds server-side rendering (SSR) streaming support. Currently, Next.js works for those who want to do SSR with React, but Next.js 12 can also benefit from streaming through React 18 (called SSR Streaming in Next.js). Seems to be out). Also, although strictly separate from React 18, Next.js 12 provides experimental support for React Server Components as well.
By supporting streaming, SSR maintains the good points of conventional SSR (especially the point that First Contentful Paint can be reached with 1 RTT), while performing performance optimization that has been used exclusively on the client side such as skeleton display. Techniques (especially techniques for improving Largest Contentful Paint) can also be incorporated.
In a nutshell, the answer is to understand Suspense. As long as you understand the concept of Suspense, you can understand Streaming SSR and React Server Components as their applications, and you will be much closer to being able to use these functions.

Understand Suspense
Let’s understand Suspense in 5 minutes. Since it only takes 5 minutes, it is not enough to explain each specific API. But only the concept. If you want to know how to use it, please see the official document and other articles.
There is only one thing to understand. In Suspense (more precisely, React 18’s Concurrent Rendering feature), the component itself can be “loading and still unable to render”.
As the most typical loading example, let’s assume a component that fetches data from an API. The traditional way would be to write a component like this (example using React Query):
const TodoList: React.VFC = () => {
const { isLoading, error, data } = useQuery('todoData', loadTodoData);
if (isLoading) {
// Displaying skeleton while loading
return <TodoListSkeleton />;
}
if (error) {
// Error procces
return <p>Hello React 18</p>;
}
return <TodoListContents data={data} />;
}
In this way, in the conventional method, the component (TodoList) is in charge of reading the data, and the information about whether it is loading or not is in the state of the component. This did not cause the “Unable to render TodoList” situation even during loading, and it was TodoList’s responsibility to render the loading UI.
On the other hand, using Suspense simplifies the responsibilities of TodoList. Specifically, the processing during loading (and the processing in case of an error) is no longer the responsibility of TodoList.
const TodoList: React.VFC = () => {
const { data } = useQuery('todoData', loadTodoData);
return <TodoListContents data={data} />;
};
So what happens during loading in this case above ? The answer is to abandon rendering. TodoList can’t render.
const App: React.VFC = () => {
return (<PageLayout>
<TodoList />
{/* TodoList can't be rendering」 */}
</PageLayout>);
};
More specifically, the React runtime tries to render the TodoList, but the TodoList interrupts rendering. Because the data is still loading (this is called component suspend). As a concrete mechanism, useQuery does it by throwing a Promise internally.
This makes TodoList to render successfully only if it has already been loaded. This is the secret to reducing TodoList’s responsibilities.
Suspense component that handles loading
Thus, “components throw renders away” is a new concept. In the above example, if TodoList throws out the rendering, the component (App) that uses it will not be able to render. There is no rendering result anyway.
When it comes to “throw out the rendering”, but in reality, this TodoList is a serious component that prepares asynchronous communication behind the scenes and retries it by itself when it is over and it is no longer in the loading state. Generally, the suspended component is a set of retries.
Therefore, React provides a component that has the role of “what to do if an internal component throws (suspends) the rendering”. That is Suspense. You can specify an alternative display when the inside is suspended by specifying fallback prop.
const App: React.VFC = () => {
return (<PageLayout>
<Suspense fallback={<TodoListSkeleton />}>
<TodoList />
{/* TodoList can't be rendering. Suspense can accept it. */}
</Suspense>
</PageLayout>);
};
This allows the App to render successfully, as Suspense handles it if the TodoList is suspended. Suppressing the influence on the outside even if the inside fails is similar to the try-catch statement.
The convenient thing about Suspense is that you can handle multiple components together. If you do the following, you can suspend any one of the pages and the whole page becomes a skeleton. Here, there are three contents in the app, but they are surrounded by one suspense.
const App: React.VFC = () => {
return (<PageLayout>
<Suspense fallback={<PageSkeleton />}>
<MyProfile />
<TodoList />
<Comments />
</Suspense>
</PageLayout>);
};
On the other hand, if you enclose each in a separate suspense, each will have its own skeleton display and will be displayed from where loading is complete.
const App: React.VFC = () => {
return (<PageLayout>
<Suspense fallback={<MyProfileSkeleton />}>
<MyProfile />
</Suspense>
<Suspense fallback={<TodoSkeleton />}>
<TodoList />
</Suspense>
<Suspense fallback={<CommentsSkeleton />}>
<Comments />
</Suspense>
</PageLayout>);
};
In this way, you can control the suspense by changing the arrangement of suspense.
Relationship between Suspense and React 18
React 18 SSR streaming assumes Suspense. SSR streaming is a method of “sending the HTML that represents the initial state (the state where the skeleton is displayed during loading) at the fastest speed, and then sending an additional HTML fragment that replaces the skeleton when the data is complete.”
This processing unit is a suspense unit. In other words, the initial state is where the fallback of Suspense is displayed. When that part is fully loaded, an HTML fragment that replaces the entire contents of Suspense is sent.
In this way, Suspense means defining “a cohesive area where asynchronous rendering takes place” (although it’s actually a bit more complicated as you can also nest Suspense).
In short, in order to utilize streaming SSR, asynchronous processing needs to be written using Suspense, and in order to control how SSR streaming progresses, Suspense needs to be placed in an appropriate position. The more finely you put the suspense around, the finer the SSR streaming will be.
React Server Components also have the potential to be inevitably suspended (from the client’s perspective) due to their “rendering is done on the server side (asynchronously)”. This means that the Server Component also needs to be wrapped in suspense.