Review
- 2024-10-01 18:38
[!Summary]
一、Introduction #
名词解释 #
在现代 Web 开发中,我们经常会听到 CSR、ISR、SSR 和 SSG 这些术语,它们代表了不同的渲染模式:
- CSR (Client-Side Rendering,客户端渲染):将网页的初始状态作为 HTML 结构发送到浏览器,然后由浏览器中的 JavaScript 动态地获取数据并渲染页面。
- SSR (Server-Side Rendering,服务器端渲染):在服务器端将整个页面渲染成完整的 HTML,然后将生成的 HTML 发送给浏览器。
- SSG (Static Site Generation,静态站点生成):在构建时将页面渲染成静态 HTML 文件,部署到服务器后直接提供给用户。
- ISR (Incremental Static Regeneration,增量静态再生):结合了 SSG 和 SSR 的优点,在构建时生成静态页面,并在运行时根据需要更新部分页面。
- SPR (Serverless Pre-Rendering,无服务预渲染):在 SSR 架构下通过预渲染与缓存能力,将部分页面转化为静态页面,以避免其在服务器接收到请求的时候频繁被渲染的能力,同时一些框架还支持设置静态资源过期时间,以确保这部分“静态页面”也能有一定的即时性。
- NSR (Native Side Rendering) 在用户即将访问页面的上级页面预取页面数据,由客户端缓存 HTML 结构,以达到用户真正访问时快速响应的效果。NSR 见于各种移动端 + Webview 的 Hybrid 场景,是需要页面与客户端研发协作的一种优化手段。
- ESR (Edge Side Rendering) ESR 就是将渲染工作交给边缘服务器节点,常见的就是 CDN 的边缘节点。这个方案主打的是边缘节点相比核心服务器与用户的距离优势,利用了 CDN 分级缓存的概念,渲染和内容填充也可以是分级进行并缓存下来的。
- DPR (Distributed Persistent Rendering) https://github.com/jamstack/jamstack.org/discussions/549
[!Summary]- 渲染模式 这里所说的 ✌🏻 渲染模式 ✌🏻,包括:
- 页面 / 应用在开发完成之后的产物编译方式;
- 部署上线之后的服务形态;
- 资源存储与分发的方式;
- 用户访问时的启动与渲染过程;
- 这几方面不同的实现和规范。
特征 #
| 特征 | CSR | SSR | SSG | ISR |
|---|---|---|---|---|
| 渲染时机 | 浏览器端 | 服务器端 | 构建时 | 构建时 + 运行时 |
| 首屏加载 | 较慢,需等待 JavaScript 执行 | 较快,直接返回 HTML | 非常快,直接返回 HTML | 非常快,直接返回 HTML |
| SEO | 不友好,搜索引擎可能无法正确抓取 | 友好,搜索引擎可以很好地抓取 | 友好,搜索引擎可以很好地抓取 | 友好,搜索引擎可以很好地抓取 |
| 动态数据 | 擅长处理 | 较复杂,需要考虑数据获取和传递 | 不擅长处理 | 擅长处理 |
| 开发体验 | 简单,热更新方便。前后端分离,服务器负担轻。 | 复杂,需要考虑服务器端和客户端的交互。 | 简单,但需要提前构建 | 介于 SSR 和 SSG 之间 |
适用场景 #
- CSR:高度交互的单页应用 (SPA)
- SSR:适用于需要良好 SEO 和首屏加载性能的应用,如电商网站等。
- SSG:适用于静态内容为主的网站或博客,如文档站点等。
- ISR:适用于内容经常更新但不需要实时性的应用,如营销活动页面等。
SSR #
服务端渲染,对于浏览器(或者 Hybrid、Webview 之下的宿主)环境的获知和相关操作就会受到局限,一些操作不得不延迟到客户端激活之后才得以进行。
React 提供的 SSR API #
Server APIs #
The
react-dom/serverAPIs let you render React components to HTML on the server. These APIs are only used on the server at the top level of your app to generate the initial HTML.
renderToPipeableStreamnewrenderToStaticNodeStreamrenderToReadableStreamnewrenderToStringrenderToStaticMarkuprenderToNodeStream已废弃
[!Summary]- Server APIs 详细介绍
renderToString(element):
- 主要用于传统的服务端渲染
- 将React组件渲染为HTML字符串
- 用于生成初始页面内容,对SEO和首屏加载很有帮助
- 这是React早期版本就存在的方法,在React 18中仍然支持
- 性能相对较低,因为需要完全渲染整个组件树
renderToStaticMarkup(element):
- 类似于
renderToString(),但会生成更少的DOM属性- 主要用于生成静态HTML,不包含React内部使用的额外属性
- 适合生成不需要交互的静态页面
- 生成的HTML体积更小
renderToPipeableStream(element):
- React 18引入的新API,专为Node.js环境设计
- 支持流式服务端渲染
- 可以在渲染过程中逐步发送HTML
- 提供更好的性能和更快的首屏加载体验
- 支持中断和恢复渲染
renderToReadableStream(element):
- React 18引入的新API
- 为Web Stream API设计,主要用于现代浏览器和边缘运行时(如Cloudflare Workers)
- 返回一个可读的流(Readable Stream)
- 支持Suspense的流式渲染
- 可以更早地开始传输HTML,提高性能
- 支持渲染中断和逐步加载
renderToStaticNodeStream(element):
- 类似于
renderToStaticMarkup(),但返回一个Node.js流- 用于生成静态HTML流
- 适合需要流式传输静态HTML的场景
- 生成的HTML不包含React内部属性
主要区别和使用场景
renderToString()是传统的同步渲染方法,将 React tree 渲染成 stringrenderToStaticMarkup()和renderToStaticNodeStream()适用无交互的场景渲染renderToPipeableStream()适用于Node.js环境,将 React tree 渲染成 Writable NodeJS StreamrenderToReadableStream()适用于支持 Web Stream API 的现代浏览器,将 React tree 渲染成 Readable Web Stream
renderToString 不足
- 服务端需要准备好所有组件的 HTML 才能返回。如果某个组件需要的数据耗时较久,就会阻塞整个 HTML 的生成。
- Hydration 是一次性的,用户需要等待客户端加载所有组件的 JavaScript 并 Hydrated 完成后才能和任一组件交互。(渲染逻辑复杂时,页面首次渲染到可交互之间可能存在较长的不可交互时间)
- 在 React SSR 中不支持客户端渲染常用的代码分割组合
React.lazy和Suspense。
renderToNodeStream React 18 废弃
基本原理是从DOM树自顶向下开始渲染,并不能等待某个组件的数据,然后先渲染其他部分的HTML。
新的流式渲染API(renderToPipeableStream()和renderToReadableStream())支持:
- 更快的首屏加载,减少白屏时间
- 支持 Suspense
- 可以在渲染过程中逐步显示内容,可随时中断和恢复渲染
- Streaming HTML:服务端可以分段传输 HTML 到浏览器,而不是像 React 18 以前一样,需要等待服务端渲染完成整个页面后才返回给浏览器。这样,浏览器可以更快的启动 HTML 的渲染,提高 FP、FCP 等性能指标。
- Selective Hydration:在浏览器端 hydration 阶段,可以只对已经完成渲染的区域做 hydration,而不需要等待整个页面渲染完成、所有组件的 JS bundle 加载完成,才能开始 hydration。这样可以更早的对已经完成渲染的区域做事件绑定,从而让页面获得更好的可交互性。
| 特点 | renderToPipeableStream | renderToReadableStream |
|---|---|---|
| 流的方向 | 可写流 | 可读流 |
| 主要用途 | 自定义渲染过程,写入响应 | 逐步读取渲染结果 |
| 适用场景 | 传统的 Express/Koa Node.js 服务器。 服务器端渲染,自定义 SSR 逻辑 | 现代Web应用,客户端处理渲染结果,数据存储 |
renderToPipeableStream: 适合在服务器端渲染时,需要自定义渲染过程或实现流式渲染的场景。renderToReadableStream: 适合在客户端需要逐步处理渲染结果,或者需要将渲染结果存储到其他地方的场景。现代无服务器 (Serverless) 架构
renderToPipeableStream 详解
#
代码示例 #
const { pipe, abort } = ReactDOMServer.renderToPipeableStream(
<App />,
{
bootstrapScriptContent: `window.__MOCK_DATA__ = ${JSON.stringify({ x: 1, y: 2 })}`,
bootstrapScripts: ["/main.js"],
identifierPrefix: '', // idprefix
onShellReady() { },
onShellError() {
res.statusCode = 500;
res.send("<!doctype html><p>Loading...</p>");
},
onAllReady() {
res.statusCode = 200;
res.setHeader("Content-type", "text/html");
pipe(res);
},
onError() { },
}
);ReactDOM.hydrateRoot(
document,
<React.StrictMode>
<App />
</React.StrictMode>
);占位符 #
不同的 id 前缀代表不同作用的元素:
- Placeholder:
P:占位块 - Segment:
S:要插入的有效片段,是一个完整的、可以独立渲染的片段 - Boundary:
B:Suspense 组件边界
identifierPrefix -> idPrefix -> ${idPrefix}P:${treeId}
- Id:
R:唯一标识符,用于区分不同的 Placeholder, Segment, Boundary
不同的注释标注开头代表不同 Suspense 边界 (Suspense boundaries)状态的块:
为了不影响 CSS 选择器和展示效果,Suspense Boundary 不是一个具体片段或者自定义标签
- Completed(已完成):
<!--$--> - Pending(等待中):
<!--$?--> - ClientRendered(客户端已渲染):
<!--$!-->
标注结束的注释则统一为<!--/$-->
详解 #
NodeJS Writable Stream
一般来说,流式渲染就是把 HTML 分块通过网络传输,然后客户端收到分块后逐步渲染,提升页面打开时的用户体验。通常是利用 HTTP/1.1 中的分块传输编码(Chunked transfer encoding)机制。
const http = require("http");
const url = require("url");
const sleep = (ms) => {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
};
const server = http.createServer(async (req, res) => {
const { pathname } = url.parse(req.url);
if (pathname === "/") {
res.statusCode = 200;
res.setHeader("Content-Type", "text/html");
res.setHeader("Transfer-Encoding", "chunked");
res.write("<html><body><div>First segment</div>");
// 手动设置延时,让分段显示的效果更加明显
await sleep(2000);
res.write("<div>Second segment</div></body></html>");
res.end();
return;
}
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("okay");
});
server.listen(8080);renderToReadableStream 详解
#
NextJS 如何支持的 SSR #
RSC (React Server Component) #
SSR 稳定性保障 #
- API不稳定:无解
- SSR服务不稳定
SSR服务不稳定
- 向下兜底CSR
- SSR+Serverless:根据QPS、CPU、内存使用率,自动秒级扩缩容
SSG #
ISR #
Reference #
Server React DOM APIs https://react.dev/reference/react-dom/server Rendering on the Web https://web.dev/articles/rendering-on-the-web 除了 CSR、SSR,还有哪五种渲染模式? https://mp.weixin.qq.com/s/JSbOdD6AmjgS_gmqeGnYmg Github ssr