Putting It All Together
In the previous lesson, we learned about Suspense's origin story, and dug into the fundamental mechanism that makes it work: we create Suspense boundaries that wrap around slices of our React application, suspending rendering until all of the components within have fetched their data. Our goal was to avoid a bunch of janky layout shifts caused by “lone wolf” components, each operating on their own schedule.
This was the original vision for Suspense, back when it was exclusively used on the client. To get to the true potential of Suspense, we have to talk about how it works in a server-side rendering context. It turns out, Suspense is useful for so much more than avoiding Spinner Hell!
Let's take another look at the Suspense boundary we drew in the previous lesson:
We set up this boundary in order to avoid duplicate loading states, but we've actually done something much more fundamental: we've drawn a circle around the slowest part of our application.
Essentially, we've split the page into two logical “chunks”:
Let's consider the implications in a server-side rendering context. In a “classic” SSR setup, without Suspense, everything happens sequentially:
This is a data visualization which shows a sequence of events between client and server. Each event is represented here as a list item.
- "Database Query" on server. Duration: 12 units of time.
- "Render App" on server. Duration: 4 units of time.
- Response from server. Duration: 4 units of time.
- "Download JS" on client. Duration: 6 units of time.
- "Hydrate" on client. Duration: 4 units of time.
In this setup, each step is blocking:
- We need to retrieve all of the data from the database before we can start rendering.
- We need to generate all of the HTML before we can send anything to the browser.
- In the browser, we need to download all of the JavaScript before we can start hydrating.
- We need to hydrate all of the components before anything is interactive.
But with Suspense, our app has been broken into multiple chunks. We can work on these chunks in parallel.
First, we'll generate and send the HTML for the first chunk. This happens quickly, since we don't have to wait on any database queries. This HTML includes the fallback
loading state for the Suspense boundary:
Then, once the database query is completed, we generate and send the rest of the HTML:
This is how we achieve the incredible performance we saw with Sole&Ankle:
Streaming SSR with Suspense
This is a data visualization which shows a sequence of events between client and server. Each event is represented here as a list item.
- "Render Skeleton" on server. Duration: 2 units of time.
- Response from server. Duration: 4 units of time.
- "Database Query" on server. Duration: 12 units of time.
- "Render Shoes" on server. Duration: 2 units of time.
- Response from server. Duration: 4 units of time.
- "Download JS" on client. Duration: 6 units of time.
- "Hydrate" on client. Duration: 2 units of time.
- "Hydrate" on client. Duration: 2 units of time.
We're able to send the HTML in chunks like this thanks to a super-power that web servers have: the ability to stream the response.
We see this all the time with videos. When you watch a video on this course platform, for example, you don't have to wait for the entire video to be received before you can start watching it. Instead, the server sends it in chunks.
And so, the modern system we've been building has a few parts:
- Server Side Rendering allows us to generate HTML on the server.
- Streaming SSR allows us to break the response into chunks, for faster performance.
- Suspense lets us define what those chunks are, by drawing boundaries around parts of the React application.
There's one more part as well: React Server Components. We'll see how they fit into this toolkit in the next lesson.
Selective hydration
Earlier in this module, we learned about hydration, the process of booting up the React app on the client so that it can adopt the server-generated HTML, making it interactive.
I demonstrated it with this cute animation:
This is a helpful way to understand hydration, but this is a very 2018 way to look at it. With Suspense and Streaming SSR, the process doesn't happen one step at a time like this.
Instead, it's much more modular:
Each Suspense boundary we create is sent as a separate chunk. Each chunk is hydrated separately, a process which starts as soon as the HTML chunk is received (and after React itself has been downloaded).
React will even prioritize which chunk to hydrate based on user activity. If the user clicks a button that hasn't yet been hydrated, React will make that the #1 priority. Once it's been hydrated, React triggers a click event on that element, giving the illusion that it's been interactive all along.
The web is about to get a heck of a lot faster, especially for folks in regions without high-speed internet!