一文了解 React 18 SSR
2022/05/03
前言
在项目里使用 SSR 也有一段时间了, 趁着五一小长假来研究一下。
文章就下面几个问题进行解答:
- 什么是 SSR?
- React 18 使 SSR 发生什么变化?
- 如何实现一个简单的 React SSR?
一些概念
SPA
SPA 即单页面应用(Single Page Application)。下面是来自维基百科的解释:
单页应用(英语:single-page application,缩写SPA)是一种网络应用程序或网站的模型,它通过动态重写当前页面来与用户交互,而非传统的从服务器重新加载整个新页面。这种方法避免了页面之间切换打断用户体验,使应用程序更像一个桌面应用程序。在单页应用中,所有必要的代码(HTML、JavaScript 和 CSS)都通过单个页面的加载而检索,或者根据需要(通常是为响应用户操作)动态装载适当的资源并添加到页面。
下面的 CSR 和 SSR 都是基于 SPA 来的。
CSR
CSR即客户端渲染(Client-Side Rendering)。
顾名思义,就是在渲染工作在客户端(浏览器)进行。
CSR 过程如下图所示:
图片源自 Shaundai 在 React Conf 2021 的演讲。
下面代码是一个CSR 单页应用的 HTML 结构:
可以看到,上面 HTML 中只有一个 <div id="root"></div>
,其页面结构需要通过加载的 main.js
来生成,数据也需要通过浏览器发送 Ajax 请求来获取。
这样做可以减轻服务端的压力,但是也会一些问题:
- 首屏渲染慢(渲染前需要下载 JS 和 CSS 等资源),有白屏问题,在 JavaScript 加载时用户会看到一个空白页面,体验差;
- 不利于 SEO,只返回一个基础的 HTML,搜索引擎一般无法获取最终的 HTML。
SSR
SSR 即 Server-Side Rendering(服务端渲染)。
SSR 就是在服务器上获取数据并处理组件来生成 HTML,并将该 HTML 发送给用户。SSR 可以使用户在应用的 JavaScript 包加载和运行之前看到页面的内容。
SSR 的过程如图所示:
React 中的 SSR 有以下几个步骤:
- 在服务器上,获取到整个应用程序的数据。
- 然后,在服务器上,将整个应用程序呈现为 HTML 并在响应中发送。
- 客户端收到数据后,在客户端上加载整个应用程序的 JavaScript 代码。
- 最后,客户端运行 JavaScript 来把事件附加到服务器生成的 HTML 上,以使页面具有交互性(这就是“hydration,以下称为‘注水’”)。
第 2 步中返回的 HTML 结构如下面所示:
虽然 hydration 之前页面的互动性不强(只有<a>
标签链接和部分表单可以响应),但是却可以让用户在 JavaScript 仍在加载时看到一些内容,这就解决了页面白屏问题,给用户更好的体验,同时因为在服务端生成完整的 HTML ,所以也解决了 SEO 的问题。
优点:
- 首屏渲染快,白屏时间短,用户体验好;
- 利于 SEO,HTML 在服务端生成,搜索引擎直接抓取到最终页面结果。
缺点:
- HTML 渲染在服务端完成,需要部署并运维服务器,增加成本;
- 需要考虑组件、库的兼容性;
- TTFB(Time To First Byte,浏览器获取到第一个字节的时间)增加。
React 18 中 SSR 的变化
在 React 18 之前的 SSR 是这样的,渲染 HTML 和 Hydration 只有两个选项——“全部”或“没有”。什么意思呢?就是服务端必须获取所有数据才相应并返回 HTML,而当客户端收到 HTML 后必须要加载全部代码来进行注水。这样就有一个问题,就是上面所说的,hydration 之前页面的互动性不强,这对用户体验有一定的影响。
React 18 中关于 SSR 的主要的变化有两点:
- 服务端上,新的流式渲染(Streaming HTML)。从原来的
renderToString
方法切换到新的renderToPipeableStream
方法即可使用。
- 客户端上,选择性注水(Selective Hydration)。需要从原来的
hydrate
切换到hydrateRoot
,然后使用<Suspense>
来包裹组件。
上述这些功能解决了 React SSR 的三个长期存在的问题:
- 不再需要等待所有数据在服务端加载后再发送HTML。相反,一旦你有足够的数据来显示应用程序的基础部分,就可以发送 HTML,并在 HTML 准备好后流式发送其余的数据;
- 不再需要等待所有的 JavaScript 加载完毕后再开始流化(streaming)。相反,您可以将代码分割与服务器渲染一起来使用。服务端 HTML 将被保留,React 将在相关代码加载时对其进行注水;
- 不再需要等待所有组件水化后开始与页面互动。相反,你可以依靠选择性注水来优先处理用户正在交互的组件,并尽早处理它们。
流式渲染(Streaming HTML)
原来的流式渲染 API renderToNodeStream
在React 18 中已经被废弃,可以使用 renderToPipeableStream
来实现流式渲染,该 API 可以使用 Streaming 和 Suspense 的全部特性。
新的流式渲染可以让服务端尽早开始响应并发送 HTML,并且流式渲染的额外内容可以与<script>
标签一起配合,把它们放在正确的地方。
来看一下它的用法:
效果如图所示:
选择性注水(Selective Hydration)
在 React 18 中被 Suspense
包裹的组件的注水过程不再阻止浏览器进行其他工作。React会先对准备就绪的部分进行注水处理,不会被未就绪的部分阻塞。
可以使用 lazy
和 Suspense
来处理那些不需要同步加载的组件:
选择性注水可以让客户端在其余的 HTML 和 JavaScript 代码被完全下载之前尽早开始对页面进行注水,并且还会根据用户交互优先考虑屏幕中最紧急的部分,创造一种即时注水的错觉。
但是需要注意的是,目前 React 18 中的 SSR <Suspense>
还不支持在请求数据时使用。
实践
具体可看项目代码,这里不再赘述。
项目 Github:https://github.com/axnir/simple-react-ssr
参考文章
Understanding Hydration in React applications(SSR)