The much anticipated React 18, (now in beta) is on the horizon and offers a new Suspense SSR Architecture.
To understand the new architecture, one must be familiar with the basic concepts like client-side rendering, server-side rendering, hydration, etc.
We have explained these concepts in our previous blog post related to Hydration. It is recommended to go through it before jumping to the new architecture.
How does SSR work?
In SSR, data is fetched, and the HTML is generated from the React components on the server. The HTML is then sent out to the client.
Here are the steps which are followed during SSR:
- Data is fetched for the entire application on the server
- The HTML is generated for the entire application from the React components on the server, which is then sent to the client.
Notice that we have emphasized the entire application in each step. This is because each step had to finish for the entire app at once before the next step could start. This is not efficient if some parts of the application are slower than others.
Let’s consider the example mentioned in the SSR WG discussion. Here, our application has NavBar, SideBar, and RightPane containing Post and Comments.
‘Comments’ part is the most important part of our application
in which users are interested.
But, let’s say
<Comments> component involves expensive API request for a large amount of data
Now, let us look into the SSR issues for this application.
What are the problems in SSR before React 18?
1. Fetch everything before showing anything
As we have seen earlier, we need to fetch all the data before showing anything to the user. This means we also need to fetch all the comments which might take time for a large amount of data. It is inefficient, as a user will not see anything on the screen.
Now, we are left with two choices -
- Delay sending the HTML from the server
- Exclude comments from the HTML. This would create an overhead on a client to render comments.
Both these options do not look good.
2. Load everything before hydrating anything
before starting to hydrate.
it would take some time to load.
Even though the JS code for NavBar, SideBar, Post is loaded, hydration cannot start.
Again, we are left with two choices -
- Delay hydrating till all the JS code is loaded. But this is not ideal.
- Use code-splitting for Comments and load it separately. This means we have to exclude comments from the server HTML. Otherwise, React won’t know what to do with this chunk of HTML and throw it away during hydration.
3. Hydrate everything before interacting with anything
Let’s say, our
<Comments> component has an expensive rendering logic,
which takes a while to attach event handlers.
As we know, hydration takes place in a single pass.
This means once it starts hydrating,
React won’t stop until it is finished.
As a result, we have to wait for all components to be hydrated
before we can interact with any of them.
Consider a case where a user clicked on a post by mistake. Now, he wants to navigate to the home page. The application is frozen due to the ongoing hydration. Due to this, the user cannot navigate, even if the Home page link is visible in the NavBar.
What a waste of time for the user!
Thanks to the new Suspense SSR architecture in React 18, which provides a solution to all the problems!
We break the work, instead of following the waterfall model -
Fetch data (server) → Render to HTML (server) → Load JS code (client) → Hydrate (client)
It enables us to follow each of these stages for a part of the screen instead of the entire app.
Let’s look into this in more detail.
Streaming HTML and Selective Hydration in React 18
Suspense is something that lets us ‘wait’ for some code to load and specify a loader while we are waiting for the code to finish loading.
There are two major SSR features in React 18 unlocked by Suspense:
Streaming HTML on the server:
To opt into it, we need to switch from
renderToStringto the new
Selective Hydration on the client:
To opt into it, we need to switch to
createRooton the client and then start wrapping parts of our app with
Sticking to the example discussed earlier,
we know that the
<Comments> component is a problem creator.
So let’s wrap it into
and tell React that until it’s ready,
React should display the
<Spinner /> component:
Streaming HTML before all the data is fetched
This way, we tell React to not wait for comments and start streaming HTML for the rest of the application. Comments would be replaced by Spinner placeholder.
When the data for the comments is ready on the server,
React will send additional HTML into the same stream,
along with the
<script> tag to put that HTML in the ‘right place’.
Even before React itself loads on the client, the HTML for comments will be loaded.
How cool is that!
This is called ‘Streaming HTML’. This is how we solve the first problem we discussed earlier- Fetch everything before showing anything.
Hydrating the page before all the code has loaded
By wrapping Comments in
we not only tell React
to unblock the rest of the page from streaming
but also from hydrating!
This is called ‘Selective Hydration’. Thanks to Selective Hydration, a heavy piece of JS doesn’t prevent the rest of the page from becoming interactive.
In the below image,
we see that
lets us hydrate the app before the
<Comments> component has loaded.
React then starts hydrating the Comments section after the JS code is loaded.
This way our second problem is solved- Load everything before hydrating anything.
Hydrating the page before all the HTML has been streamed
One more benefit from wrapping Comments in
is that hydration no longer blocks user interactivity!
In the below image,
we see that we are able to click on the SideBar,
even when the
<Comments> component is hydrating.
Suppose, we have multiple components wrapped in
React will attempt to hydrate both of them, starting with the Suspense boundary that it finds earlier in the tree ( SideBar in this case ).
Let’s say the user starts interacting with the comments section, for which the code is also loaded. In this case, React will prioritize hydrating the comments assuming it to be more urgent and makes the comment section interactive. After that, it will continue hydrating the Sidebar.
This solves our third problem - Hydrate everything before interacting with anything
These under the hood improvements in the
<Suspense> component have solved a lot of SSR issues.
Thanks to the React team for doing so much work on Suspense!
More details about these changes can be found in WG discussion.