react-selective-hydration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Selective Hydration

选择性Hydration

In previous articles, we covered how SSR with hydration can improve user experience. React is able to (quickly) generate a tree on the server using the
renderToString
method that the
react-dom/server
library provides, which gets sent to the client after the entire tree has been generated. The rendered HTML is non interactive, until the JavaScript bundle has been fetched and loaded, after which React walks down the tree to hydrate and attaches the handlers.
However, this approach can lead to some performance issues due to some limitations with the current implementation.
在之前的文章中,我们介绍了带hydration的SSR如何提升用户体验。React能够借助
react-dom/server
库提供的
renderToString
方法,在服务端快速生成一棵DOM树,整棵树生成完成后再发送给客户端。渲染出的HTML是无交互性的,直到JavaScript bundle被获取并加载完成,React才会遍历树进行hydration并绑定事件处理程序。
然而,当前实现的一些局限性会导致这种方法存在部分性能问题。

When to Use

适用场景

  • Use this when you want to make parts of your SSR page interactive before all JavaScript has loaded
  • This is helpful when slow components (e.g., data-fetching components) are blocking the entire page's hydration
  • 当你希望在所有JavaScript加载完成前,让SSR页面的部分区域具备交互性时使用
  • 当加载缓慢的组件(例如数据获取组件)阻塞了整个页面的hydration时,这种方法会很有帮助

Instructions

操作步骤

  • Use
    Suspense
    boundaries to delineate independently hydratable chunks of UI
  • Use
    renderToPipeableStream
    (Node) or
    renderToReadableStream
    (edge) for streaming SSR
  • Place heavy data-fetching components inside
    Suspense
    so they don't delay sibling hydration
  • Ensure critical interactive components are not inside long-lived loading fallbacks
  • Use
    hydrateRoot
    (React 18+) to benefit from selective hydration
  • 使用
    Suspense
    边界划分可独立hydrate的UI区块
  • 使用
    renderToPipeableStream
    (Node环境)或
    renderToReadableStream
    (边缘环境)进行流式SSR
  • 将数据获取繁重的组件放在
    Suspense
    内部,避免延迟兄弟组件的hydration
  • 确保关键交互组件不在长期存在的加载回退组件内部
  • 使用
    hydrateRoot
    (React 18+)以充分利用选择性hydration的优势

Details

详细说明

Before the server-rendered HTML tree is able to get sent to the client, all components need to be ready. This means that components that may rely on an external API call or any process that could cause some delays, might end up blocking smaller components from being rendered quickly.
Besides a slower tree generation, another issue is the fact that React only hydrates the tree once. This means that before React is able to hydrate any of the components, it needs to have fetched the JavaScript for all of the components before it's able to hydrate any of them. This means that smaller components (with smaller bundles) have to wait for the larger components's code to be fetched and loaded, until React is able to hydrate anything on your website. During this time, the website remained non-interactive.
React 18 solves these problems by allowing us to combine streaming server-side rendering with a new approach to hydration: Selective Hydration!
Instead of using
renderToString
, modern React SSR uses
renderToPipeableStream()
on Node runtimes or
renderToReadableStream()
on Web Stream runtimes.
These APIs, in combination with
hydrateRoot()
and
Suspense
, make it possible to start streaming HTML without having to wait for the larger components to be ready. This means that we can lazy-load components when using SSR without blocking the hydration of the rest of the page.
The
Comments
component, which earlier slowed down the tree generation and TTI, is now wrapped in
Suspense
. This tells React to not let this component slow down the rest of the tree generation. Instead, React inserts the fallback components as the initially rendered HTML, and continues to generate the rest of the tree before it's sent to the client.
In the meantime, we're still fetching the external data that we need for the
Comments
component.
Selective hydration makes it possible to already hydrate the components that were sent to the client, even before the
Comments
component has been sent!
Once the data for the
Comments
component is ready, React starts streaming the HTML for this component, as well as a small
<script>
to replace the fallback loader.
React starts the hydration after the new HTML has been injected.
React 18 fixes some issues that people often encountered when using SSR with React.
Streaming rendering allows you to start streaming components as soon as they're ready, without risking a slower FCP and TTI due to components that might take longer to generate on the server.
Components can be hydrated as soon as they're streamed to the client, since we no longer have to wait for all JavaScript to load to start hydrating and can start interacting with the app before all components have been hydrated.
Note (React 18+): Best Practices for Selective Hydration
Use Suspense boundaries in your SSR code to delineate independent chunks of UI—each Suspense boundary can hydrate independently. Structure your code so that any heavy data-fetching component is inside a Suspense, so it doesn't delay the initial HTML or hydration of siblings. Next.js automatically wraps each route segment in a Suspense boundary for you (via
loading.js
).
If writing your own SSR, use
renderToPipeableStream()
(or
renderToReadableStream
) instead of the older
renderToNodeStream
, and provide an
onShellReady
callback to flush early HTML. React will attach event listeners progressively as chunks come in.
Ensure critical interactive components are not inside a loading fallback when they appear on screen, or if they are, that the fallback is very short-lived. If a user sees a button, it should be hydratable immediately. Verify you're not using legacy APIs—use
hydrateRoot
correctly for React 18+ to benefit from these performance boosts.
在服务端渲染的HTML树能够发送给客户端之前,所有组件都需要准备就绪。这意味着那些依赖外部API调用或任何可能导致延迟的流程的组件,可能会阻碍小型组件快速渲染。
除了树生成速度较慢之外,另一个问题是React只会对树进行一次hydration。这意味着在React能够对任何组件进行hydration之前,它需要先获取所有组件的JavaScript代码。这就意味着小型组件(对应更小的bundle)必须等待大型组件的代码被获取并加载完成,React才能对网站上的任何内容进行hydration。在此期间,网站始终处于无交互状态。
React 18通过允许我们将流式服务端渲染与一种新的hydration方法——选择性Hydration相结合,解决了这些问题!
现代React SSR不再使用
renderToString
,而是在Node运行时使用
renderToPipeableStream()
,在Web Stream运行时使用
renderToReadableStream()
这些API与
hydrateRoot()
Suspense
结合使用,使得无需等待大型组件准备就绪即可开始流式传输HTML。这意味着我们在使用SSR时可以懒加载组件,而不会阻塞页面其余部分的hydration。
之前拖慢树生成速度和TTI的
Comments
组件,现在被包裹在
Suspense
中。这会告知React不要让该组件拖慢其余树的生成速度。相反,React会将回退组件作为初始渲染的HTML插入,并在发送给客户端之前继续生成树的其余部分。
与此同时,我们仍在获取
Comments
组件所需的外部数据。
选择性Hydration使得即使在
Comments
组件被发送之前,我们就可以对已发送到客户端的组件进行hydration!
一旦
Comments
组件的数据准备就绪,React就会开始流式传输该组件的HTML,以及一段用于替换回退加载器的小型
<script>
脚本。
新HTML注入完成后,React开始进行hydration。
React 18修复了人们在使用React SSR时经常遇到的一些问题。
流式渲染允许你在组件准备就绪后立即开始流式传输,不会因服务端生成耗时较长的组件而导致FCP和TTI变慢。
组件一被流式传输到客户端就可以进行hydration,因为我们不再需要等待所有JavaScript加载完成才能开始hydration,并且可以在所有组件完成hydration之前就与应用进行交互。
注意(React 18+):选择性Hydration最佳实践
在你的SSR代码中使用Suspense边界划分独立的UI区块——每个Suspense边界都可以独立进行hydration。构建代码时,将任何数据获取繁重的组件放在Suspense内部,避免延迟初始HTML的生成或兄弟组件的hydration。Next.js会自动通过
loading.js
为每个路由段包裹一个Suspense边界。
如果自行编写SSR代码,请使用
renderToPipeableStream()
(或
renderToReadableStream
)替代旧版的
renderToNodeStream
,并提供
onShellReady
回调以提前刷新HTML。React会随着区块的到来逐步附加事件监听器。
确保关键交互组件不在加载回退组件内部(当它们显示在屏幕上时),如果必须放在内部,也要确保回退组件的存在时间非常短。如果用户看到一个按钮,它应该能够立即被hydrate。确认你没有使用旧版API——正确使用React 18+的
hydrateRoot
以获得这些性能提升。

Source

来源

References

参考资料