Skip to content
3

New Suspense SSR Architecture in React 18 #37

gaearon announced in Deep Dive
New Suspense SSR Architecture in React 18 #37
Jun 5, 2021 · 7 comments · 12 replies

@gaearon
gaearon Jun 5, 2021
Maintainer

Overview

React 18 will include architectural improvements to React server-side rendering (SSR) performance. These improvements are substantial and are the culmination of several years of work. Most of these improvements are behind-the-scenes, but there are some opt-in mechanisms you’ll want to be aware of, especially if you don’t use a framework.

The primary new API is pipeToNodeWritable, which you can read about in Upgrading to React 18 on the Server. We plan to write more about it in detail as it's not final and there are things to work out.

The primary existing API is <Suspense>.

This page is a high-level overview of the new architecture, its design, and the problems it solves.

tl;dr

Server-side rendering (abbreviated to “SSR” in this post) lets you generate HTML from React components on the server, and send that HTML to your users. SSR lets your users see the page’s content before your JavaScript bundle loads and runs.

SSR in React always happens in several steps:

  • On the server, fetch data for the entire app.
  • Then, on the server, render the entire app to HTML and send it in the response.
  • Then, on the client, load the JavaScript code for the entire app.
  • Then, on the client, connect the JavaScript logic to the server-generated HTML for the entire app (this is “hydration”).

The key part is that 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 your app are slower than others, as is the case in pretty much every non-trivial app.

React 18 lets you use <Suspense> to break down your app into smaller independent units which will go through these steps independently from each other and won’t block the rest of the app. As a result, your app’s users will see the content sooner and be able to start interacting with it much faster. The slowest part of your app won’t drag down the parts that are fast. These improvements are automatic, and you don’t need to write any special coordination code for them to work.

This also means that React.lazy "just works" with SSR now. Here's a demo.

(If you don’t use a framework, you will need to change the exact way the HTML generation is wired up.)

What Is SSR?

When the user loads your app, you want to show a fully interactive page as soon as possible:

This illustration uses the green color to convey that these parts of the page are interactive. In other words, all their JavaScript event handlers are already attached, clicking buttons can update the state, and so on.

However, the page can’t be interactive before the JavaScript code for it fully loads. This includes both React itself and your application code. For non-trivial apps, much of the loading time will be spent downloading your application code.

If you don’t use SSR, the only thing the user will see while JavaScript is loading is a blank page:

This is not great, and this is why we recommend using SSR. SSR lets you render your React components on the server into HTML and send it to the user. HTML is not very interactive (aside from simple built-in web interactions like links and form inputs). However, it lets the user see something while the JavaScript is still loading:

Here, the grey color illustrates that these parts of the screen are not fully interactive yet. The JavaScript code for your app has not loaded yet, so clicking buttons doesn’t do anything. But especially for content-heavy websites, SSR is extremely useful because it lets users with worse connections start reading or looking at the content while JavaScript is loading.

When both React and your application code loads, you want to make this HTML interactive. You tell React: “Here’s the App component that generated this HTML on the server. Attach event handlers to that HTML!” React will render your component tree in memory, but instead of generating DOM nodes for it, it will attach all the logic to the existing HTML.

This process of rendering your components and attaching event handlers is known as “hydration”. It’s like watering the “dry” HTML with the “water” of interactivity and event handlers. (Or at least, that’s how I explain this term to myself.)

After hydration, it’s “React as usual”: your components can set state, respond to clicks, and so on:

You can see that SSR is kind of a “magic trick”. It doesn’t make your app fully interactive faster. Rather, it lets you show a non-interactive version of your app sooner, so that the user can look at the static content while they wait for JS to load. However, this trick makes a huge difference for people with poor network connections, and improves the perceived performance overall. It also helps you with search engine ranking, both due to easier indexing and due to better speed.

Note: don’t confuse SSR with Server Components. Server Components are a more experimental feature that is still in research and likely won’t be a part of the initial React 18 release. You can learn about Server Components here. Server Components are complementary to SSR, and will be a part of the recommended data fetching approach, but this post is not about them.

What Are the Problems with SSR Today?

The approach above works, but in many ways it’s not optimal.

You have to fetch everything before you can show anything

One problem with SSR today is that it does not allow components to “wait for data”. With the current API, by the time you render to HTML, you must already have all the data ready for your components on the server. This means that you have to collect all the data on the server before you can start sending any HTML to the client. This is quite inefficient.

For example, let’s say you want to render a post with comments. The comments are important to show early, so you want to include them in the server HTML output. But your database or API layer is slow, which is out of your control. Now you have to make some hard choices. If you exclude them from the server output, the user won’t see them until JS loads. But if you include them in the server output, you have to delay sending the rest of the HTML (for example, the navigation bar, the sidebar, and even the post content) until the comments have loaded and you can render the full tree. This is not great.

As a side note, some data fetching solutions repeatedly try to render the tree to HTML and throw away the result until the data has been resolved because React doesn't provide a more ergonomic option. We’d like to provide a solution that doesn’t require such extreme compromises.

You have to load everything before you can hydrate anything

After your JavaScript code loads, you’ll tell React to “hydrate” the HTML and make it interactive. React will “walk” the server-generated HTML while rendering your components, and attach the event handlers to that HTML. For this to work, the tree produced by your components in the browser must match the tree produced by the server. Otherwise React can’t “match them up!” A very unfortunate consequence of this is that you have to load the JavaScript for all components on the client before you can start hydrating any of them.

For example, let’s say that the comments widget contains a lot of complex interaction logic, and it takes a while to load JavaScript for it. Now you have to make a hard choice again. It would be good to render comments on the server to HTML in order to show them to the user early. But because hydration can only be done in a single pass today, you can’t start hydrating the navigation bar, the sidebar, and the post content until you’ve loaded the code for the comments widget! Of course, you could use code splitting and load it separately, but you would have to exclude comments from the server HTML. Otherwise React won’t know what to do with this chunk of HTML (where’s the code for it?) and delete it during hydration.

You have to hydrate everything before you can interact with anything

There is a similar issue with hydration itself. Today, React hydrates the tree in a single pass. This means that once it’s started hydrating (which is essentially calling your component functions), React won’t stop until it’s finished doing this for the entire tree. As a consequence, you have to wait for all components to be hydrated before you can interact with any of them.

For example, let’s say the comments widget has expensive rendering logic. It might work fast on your computer, but on a low-end device running all of that logic is not cheap and may even lock up the screen for several seconds. Of course, ideally we wouldn’t have such logic on the client at all (and that’s something that Server Components can help with). But for some logic it’s unavoidable because it determines what the attached event handlers should do and is essential to interactivity. As a result, once the hydration starts, the user can’t interact with the navigation bar, the sidebar, or the post content, until the full tree is hydrated. For navigation, this is especially unfortunate since the user may want to navigate away from this page altogether—but since we’re busy hydrating, we’re keeping them on the current page they no longer care about.

How can we solve these problems?

There is one thing in common between these problems. They force you to choose between doing something early (but then hurting UX because it blocks all other work), or doing something late (but hurting UX because you’ve wasted time).

This is because there is a “waterfall”: fetch data (server) → render to HTML (server) → load code (client) → hydrate (client). Neither of the stages can start until the previous stage has finished for the app. This is why it’s inefficient. Our solution is to break the work apart so that we can do each of these stages for a part of the screen instead of entire app.

This is not a novel idea: for example, Marko is one of JavaScript web frameworks that implements a version of this pattern. The challenge was in how to adapt a pattern like this to the React programming model. It took a while to solve. We introduced the <Suspense> component for this purpose in 2018. When we introduced it, we only supported it for lazy-loading code on the client. But the goal was to integrate it with the server rendering and solve these problems.

Let’s see how to use <Suspense> in React 18 to solve these issues.

React 18: Streaming HTML and Selective Hydration

There are two major SSR features in React 18 unlocked by Suspense:

  • Streaming HTML on the server. To opt into it, you’ll need to switch from renderToString to the new pipeToNodeWritable method, as described here.
  • Selective Hydration on the client. To opt into it, you’ll need to switch to createRoot on the client and then start wrapping parts of your app with <Suspense>.

To see what these features do and how they solve the above problems, let’s return to our example.

Streaming HTML before all the data is fetched

With today’s SSR, rendering HTML and hydration are “all or nothing”. First you render all HTML:

<main>
  <nav>
    <!--NavBar -->
    <a href="/">Home</a>
   </nav>
  <aside>
    <!-- Sidebar -->
    <a href="/profile">Profile</a>
  </aside>
  <article>
    <!-- Post -->
    <p>Hello world</p>
  </article>
  <section>
    <!-- Comments -->
    <p>First comment</p>
    <p>Second comment</p>
  </section>
</main>

The client eventually receives it:

Then you load all the code and hydrate the entire app:

But React 18 gives you a new possibility. You can wrap a part of the page with <Suspense>.

For example, let’s wrap the comment block and tell React that until it’s ready, React should display the <Spinner /> component:

<Layout>
  <NavBar />
  <Sidebar />
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>
  </RightPane>
</Layout>

By wrapping <Comments> into <Suspense>, we tell React that it doesn’t need to wait for comments to start streaming the HTML for the rest of the page. Instead, React will send the placeholder (a spinner) instead of the comments:

Comments are nowhere to be found in the initial HTML now:

<main>
  <nav>
    <!--NavBar -->
    <a href="/">Home</a>
   </nav>
  <aside>
    <!-- Sidebar -->
    <a href="/profile">Profile</a>
  </aside>
  <article>
    <!-- Post -->
    <p>Hello world</p>
  </article>
  <section id="comments-spinner">
    <!-- Spinner -->
    <img width=400 src="spinner.gif" alt="Loading..." />
  </section>
</main>

The story doesn’t end here. When the data for the comments is ready on the server, React will send additional HTML into the same stream, as well as a minimal inline <script> tag to put that HTML in the “right place”:

<div hidden id="comments">
  <!-- Comments -->
  <p>First comment</p>
  <p>Second comment</p>
</div>
<script>
  // This implementation is slightly simplified
  document.getElementById('sections-spinner').replaceChildren(
    document.getElementById('comments')
  );
</script>

As a result, even before React itself loads on the client, the belated HTML for comments will “pop in”:

This solves our first problem. Now you don’t have to fetch all the data before you can show anything. If some part of the screen delays the initial HTML, you don’t have to choose between delaying all HTML or excluding it from HTML. You can just allow that part to “pop in” later in the HTML stream.

Unlike traditional HTML streaming, it doesn’t have to happen in the top-down order. For example, if the sidebar needs some data, you can wrap it in Suspense, and React will emit a placeholder and continue with rendering the post. Then, when the sidebar HTML is ready, React will stream it along with the <script> tag that inserts it in the right place— even though the HTML for the post (which is further in the tree) has already been sent! There is no requirement that data loads in any particular order. You specify where the spinners should appear, and React figures out the rest.

Note: for this to work, your data fetching solution needs to integrate with Suspense. Server Components will integrate with Suspense out of the box, but we will also provide a way for standalone React data fetching libraries to integrate with it.

Hydrating the page before all the code has loaded

We can send the initial HTML earlier, but we still have a problem. Until the JavaScript code for the comments widget loads, we can’t start hydrating our app on the client. If the code size is large, this can take a while.

To avoid large bundles, you would usually use "code splitting": you would specify that a piece of code doesn't need to load synchronously, and your bundler will split it off into a separate <script> tag.

You can use code splitting with React.lazy to split off the comments code from the main bundle:

import { lazy } from 'react';

const Comments = lazy(() => import('./Comments.js'));

// ...

<Suspense fallback={<Spinner />}>
  <Comments />
</Suspense>

Previously, this did not work with server rendering. (To the best of our knowledge, even popular workarounds forced you to choose between either opting out of SSR for code-split components or hydrating them after all their code loads, somewhat defeating the purpose of code splitting.)

But in React 18, <Suspense> lets you hydrate the app before the comment widget has loaded.

From the user’s perspective, initially they see non-interactive content that streams in as HTML:

Then you tell React to hydrate. The code for comments isn’t there yet, but it’s okay:

This is an example of Selective Hydration. By wrapping Comments in <Suspense>, you told React that they shouldn’t block the rest of the page from streaming—and, as it turns out, from hydrating, too! This means the second problem is solved: you no longer have to wait for all the code to load in order to start hydrating. React can hydrate parts as they’re being loaded.

React will start hydrating the comments section after the code for it has finished loading:

Thanks to Selective Hydration, a heavy piece of JS doesn’t prevent the rest of the page from becoming interactive.

Hydrating the page before all the HTML has been streamed

React handles all of this automatically, so you don’t need to worry about things happening in an unexpected order. For example, maybe the HTML takes a while to load even as it’s being streamed:

If the JavaScript code loads earlier than all HTML, React doesn’t have a reason to wait! It will hydrate the rest of the page:

When the HTML for the comments loads, it will appear as non-interactive because JS is not there yet:

Finally, when the JavaScript code for the comments widget loads, the page will become fully interactive:

Interacting with the page before all the components have hydrated

There is one more improvement that happened behind the scenes when we wrapped comments in a <Suspense>. Now their hydration no longer blocks the browser from doing other work.

For example, let’s say the user clicks the sidebar while the comments are being hydrated:

In React 18, hydrating content inside Suspense boundaries happens with tiny gaps in which the browser can handle events. Thanks to this, the click is handled immediately, and the browser doesn’t appear stuck during a long hydration on a low-end device. For example, this lets the user navigate away from the page they’re no longer interested in.

In our example, only comments are wrapped in Suspense, so hydrating the rest of the page happens in a single pass. However, we could fix this by using Suspense in more places! For example, let’s wrap the sidebar as well:

<Layout>
  <NavBar />
  <Suspense fallback={<Spinner />}>
    <Sidebar />
  </Suspense>
  <RightPane>
    <Post />
    <Suspense fallback={<Spinner />}>
      <Comments />
    </Suspense>
  </RightPane>
</Layout>

Now both of them can be streamed from the server after the initial HTML containing the navbar and the post. But this also has a consequence on hydration. Let’s say the HTML for both of them has loaded, but the code for them has not loaded yet:

Then, the bundle containing the code for both the sidebar and the comments loads. React will attempt to hydrate both of them, starting with the Suspense boundary that it finds earlier in the tree (in this example, it’s the sidebar):

But let’s say the user starts interacting with the comments widget, for which the code is also loaded:

React will record that the click happened, and prioritize hydrating the comments instead because it’s more urgent:

After the comments have hydrated, React “replay” the recorded click event (by dispatching it again) and let your component respond to the interaction. Then, now that React has nothing urgent to do, React will hydrate the sidebar:

This solves our third problem. Thanks to Selective Hydration, we don’t have to “hydrate everything in order to interact with anything”. React starts hydrating everything as early as possible, and it prioritizes the most urgent part of the screen based on the user interaction. The benefits of Selective Hydration become more obvious if you consider that as you adopt Suspense throughout your app, the boundaries will become more granular:

In this example, the user clicks the first comment just as the hydration starts. React will prioritize hydrating the content of all parent Suspense boundaries, but will skip over any of the unrelated siblings. This creates an illusion that hydration is instant because components on the interaction path get hydrated first. React will hydrate the rest of the app right after.

In practice, you would likely add Suspense close to the root of your app:

<Layout>
  <NavBar />
  <Suspense fallback={<BigSpinner />}>
    <Suspense fallback={<SidebarGlimmer />}>
      <Sidebar />
    </Suspense>
    <RightPane>
      <Post />
      <Suspense fallback={<CommentsGlimmer />}>
        <Comments />
      </Suspense>
    </RightPane>
  </Suspense>
</Layout>

With this example, the initial HTML could include the <NavBar> content, but the rest would stream in and hydrate in parts as soon the associated code is loaded, prioritizing the parts that the user has interacted with.

Note: You might be wondering how your app can work in this not-fully-hydrated state. There are a few subtle details in the design that make it work. For example, instead of hydrating each individual component separately, hydration happens for entire <Suspense> boundaries. Since <Suspense> is already used for content that doesn't appear right away, your code is resilient to its children not being immediately available. React always hydrates in the parent-first order, so the components always have their props set. React holds off from dispatching events until the entire parent tree from the point of the event is hydrated. Finally, if a parent updates in a way that causes the not-yet-hydrated HTML to become stale, React will hide it and replace it with the fallback you specified until the code has loaded. This ensures the tree appears consistent to the user. You don’t need to think about it, but that’s what makes it work.

Demo

We've prepared a demo you can try to see how the new Suspense SSR Architecture works. It is artifically slowed down, so you can adjust the delays in server/delays.js:

  • API_DELAY lets you make the comments take longer to fetch on the server, showcasing how the rest of the HTML can be sent early.
  • JS_BUNDLE_DELAY lets you delay the <script> tags from loading, showcasing how the comment widget’s HTML "pops in" even before React and your application bundle have been downloaded.
  • ABORT_DELAY lets you see the server "giving up" and handing off rendering to the client if fetching on the server takes too long.

In Conclusion

React 18 offers two major features for SSR:

  • Streaming HTML lets you start emitting HTML as early as you’d like, streaming HTML for additional content together with the <script> tags that put them in the right places.
  • Selective Hydration lets you start hydrating your app as early as possible, before the rest of the HTML and the JavaScript code are fully downloaded. It also prioritizes hydrating the parts the user is interacting with, creating an illusion of instant hydration.

These features solve three long-standing problems with SSR in React:

  • You no longer have to wait for all the data to load on the server before sending HTML. Instead, you start sending HTML as soon as you have enough to show a shell of the app, and stream the rest of the HTML as it’s ready.
  • You no longer have to wait for all JavaScript to load to start hydrating. Instead, you can use code splitting together with server rendering. The server HTML will be preserved, and React will hydrate it when the associated code loads.
  • You no longer have to wait for all components to hydrate to start interacting with the page. Instead, you can rely on Selective Hydration to prioritize the components the user is interacting with, and hydrate them early.

The <Suspense> component serves as an opt-in for all of these features. The improvements themselves are automatic inside React and we expect them to work with the majority of existing React code. This demonstrates the power of expressing loading states declaratively. It may not look like a big change from if (isLoading) to <Suspense>, but it's what unlocks all of these improvements.

Replies

7 comments
·
12 replies
1

This is a superb explanation of a very complex topic. Thank you!

0 replies
1

Really helpful post especially in response to "Is Suspense just a fancy loading spinner?".

Isn't event replaying completely new in React 18? Maybe I'm over-valueing it but I think it's not emphasized enough.

My journey through this post colorized

2 replies
@gaearon

gaearon Jun 7, 2021
Maintainer Author

Yeah, event replaying is new. We’re going to make some changes to how it works between Alpha and Stable so maybe describing it in detail is a bit early. But in summary, before dispatching an event, React checks if it’s “blocked” because of any parent unhydrated boundaries, in which case it gets queued. Then, when the boundary it was “blocked on” has been hydrated, the event handlers fire.

@sebmarkbage

Note that out of the box this requires at least React to have been loaded already. It's best practice to isolate a bundle that can load React at fast as possible without any code. Rather than having a bundle that includes all of React and all of the shell of the app. We'll likely add a feature to add a smaller script that can record events that get grandfathered in but we've found this not to be really necessary because that gap is so small if you extract React loading from the rest of the shell.

This feature becomes necessary from doing asynchronous hydration. In old React you wouldn't necessarily need these because if the hydration starts it blocks any browser events from firing and then when they do fire React is already hydrated. It just takes a while.

It's because we're now able to start hydration early that we need to deal with this scenario. Most of the time we just synchronously hydrate early only the thing that was interacted with. Most of the time that will be enough.

It's an edge case where you interact quickly enough and the code hasn't loaded and we have to deal with that.

Without the grandfathering, event replaying isn't a new "feature" by itself. It's just solving a side-effect of making hydration start earlier (and therefore finish earlier) and making it possible to do selectively. Those are the new features that give benefits.

1

This is an awesome detailed explanation 💯 🎉

In React 18, hydrating content inside Suspense boundaries happens with tiny gaps in which the browser can handle events. Thanks to this, the click is handled immediately, and the browser doesn’t appear stuck during long hydration on a low-end device. For example, this lets the user navigate away from the page they’re no longer interested in.

How do you make sure that the browser is still interactive during hydration? Is it like doing hydration in batches at some intervals?

Let's say we have a component A (hydrated so clickable now) and a component B which is still hydrating (both are siblings).
Clicking on component A updates some state which B is subscribed to as well so If user clicks on comp A. and B is still hydrating so what will happen in this case ? will React not dispatch the event until B is hydrated?

2 replies
@gaearon

gaearon Jun 7, 2021
Maintainer Author

How do you make sure that the browser is still interactive during hydration?

That's a great question. Would you mind reposting it as a separate Q&A thread? The answer would be relevant for other features (not only hydration) so it would be good to have a top-level link to refer to in the future. It's fine if you keep the question itself scoped to hydration there — I'd just like it to not get lost in this thread.

@ad1992

Sure, done #38

1

This is really exciting! Potentially, I suppose this means that urql's approach may work with little to no changes. I'm a little curious how data providers can retrieve the information that they're providing on the server-side.

Are you planning to extract changes to React internal caches and serialise those? or provide a different method to provide serialised data on the server-side?

Overall, on another note, I'm really hyped about this, since this gives us a path to deprecate and replace react-ssr-prepass and the "all or nothing" approach in general.

1 reply
@gaearon

gaearon Jun 8, 2021
Maintainer Author

Are you planning to extract changes to React internal caches and serialise those? or provide a different method to provide serialised data on the server-side?

The way the data transfer will work exactly is still up in the air.

When Server Components are ready (which is out of scope of React 18 but is expected to become our focus soon after), they will be the primary recommended data fetching solution for many cases. What’s curious about them is that they don’t require transferring the cache at all. This is because their rendering output is their “data.” The client won’t need to render them so there’s no need to transfer anything other than the rendering output.

For solutions that require a client cache, you can do what you already do today, and fetch data early before sending it. However, that’s not ideal so we will be exploring how to integrate them with streaming. We expect to have more clarity on this in the coming months as the release gets closer, but we’re not sure yet. You’re also welcome to explore different options and give us feedback on what works or doesn’t.

I'm really hyped about this, since this gives us a path to deprecate and replace react-ssr-prepass and the "all or nothing" approach in general.

Exactly!

1

this is really exciting - thank you for posting such a great explanation!

i know data fetching support is still incomplete, but one thing i wanted to confirm was the default behavior for the following use case:

<Suspense fallback={<Spinner />}
  <SomeComponentThatDependsOnData />
</Suspense>

Is my understanding correct that when the client goes to hydrate, it will also fetch the data dependency and trigger the Suspense boundary, but instead of showing the fallback UI, it will just show the existing html that was already streamed down from the server?

1 reply
@sebmarkbage

Yea it'll show the existing HTML until the data is available. Note that the data needs to be the same exact data as was from the server. So typically you'd not "fetch" anything there but instead suspend on waiting on more data to come from the HTML stream (e.g. embedded script tags).

1

@gaearon
gaearon Jun 8, 2021
Maintainer Author

What about SEO / Status Codes?

I got an interesting question from Twitter:

One thing that’s prevented us from streaming HTML is not knowing the status code until we’re done rendering; It could be a 200, 404, 500 or even 301. Perhaps a similar issue to not knowing the title, but would be interesting to know if React 18 has considered this?

I want to reproduce it here since it came up a few times in other conversations.

6 replies
@gaearon

gaearon Jun 8, 2021
Maintainer Author

From my understanding, the strategy is:

  1. You rely on the onError callback to gather some information before sending the initial "shell", which happens in onReadyToStream. The demo actually shows setting the status code.
  2. After you've sent the initial headers, if you discover a problem later on (e.g. rendering deeply), that piece will retry rendering on the client (and if it errors, that error will be a client error). This won't set the status code, of course.
  3. For SEO specifically, where the correct status code is extra important, you can use onCompleteAll instead of onReadyToStream as the place where you flush the stream. By that point, you'll definitely know if it errored or not. However, that also delays when you start giving content to the bot, and giving it earlier may give you better rankings due to perf. So we expect this to be something different apps may decide to do differently based on their SEO strategy.

I hope I have not misrepresented anything, @sebmarkbage can correct.

@sebmarkbage

The same principle applies to status codes, title and open graph meta data. In addition to that, the use of script tags to show content wouldn't be seen by a script-less Googlebot. Googlebot has been known to reindex pages faster when the content is available without script.

This is basically only an issue for bots. For anything else you can send script tags that update it later. Same as if you do client navigation. If you can't, it probably should be a global config for your whole site anyway. Eg if it's amp.

In general the principle is that you can wait until you have enough data before starting the stream.

It's typically good practice to throttle a bit anyway. If it completes in time you don't have an issue. It just won't be guaranteed.

Another strategy if you know that some Suspense boundaries don't contain content that influence bots you can always start streaming once you only have those left.

Another strategy you can use is to special case bots. For example if you have identifiable bots you can wait for the onCompleteAll callback and it'll wait to emit until you've completed all content. Effectively disabling streaming. But non-bots would see the stream.

That might not be best though since bots can sometimes favor fast code over complete content.

So that's a trade off you can choose. But even if you don't stream the bytes. You still benefit from interleaved computation. Parallelism.

@devknoll

devknoll Jun 8, 2021
Collaborator

For what it’s worth, the strategy we’re planning for Next.js is to dynamic render. We’ll only serve a streaming response to real clients, with appropriate no-cache headers, a 200 status code, etc.

1

@sebmarkbage
sebmarkbage Jun 9, 2021
Maintainer

The general principle of hydration here is that you can think of it as lazy. In that something only needs to hydrate at all if it's being interacted with.

However hydration is also used for other things like starting subscriptions to changes, auto playing video with more control, shifting an hscroll on a timer etc.

It's also worth hydrating just to have it warmed up so it response quicker.

That's why we also eagerly hydrate things at the lowest priority in the background too.

However sometimes some things are more important to hydrate early than others.

There's an API (which might change) called ReactDOM.unstable_scheduleHydration(node) that does this. It takes a DOM node and then it prioritizes the path to that node for hydration.

You can use this to do user space features like hydrate the content in viewport faster than other content, or hydrating that node that you know contains auto-playing video.

0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
🐳
Deep Dive
Labels
None yet
7 participants
Beta