# 浅谈前端渲染模式

> Site index: <https://blog.suxiong.me/llms.txt>

Original: [浅谈前端渲染模式](https://blog.suxiong.me/blog/5-rendering-modes)

Author: Suxiong

Date: 2023-02-16

探讨前端渲染模式的发展和变化，包括客户端渲染、服务端渲染、静态站点生成和混合渲染等。

Tags: 前端 / 渲染模式 / React / Next.js

## 前言

近期，React正式发布了全新的React官方文档——[react.dev](https://react.dev/)。目前只发布了英文版，中文版并未上线。

在新文档中有两个特点：

1. 全面拥抱`Hooks`
2. 全面拥抱`full-stack`

全面拥抱`Hooks`。React旧官方文档是基于`class component`讲解，但自从`v16.8`出现`Hooks`后，`Hooks`逐渐成为了React主要发展方向，并且新特性也是基于`Hooks`开发，所以新文档全面拥抱`Hooks`是大势所趋。

全面拥抱`full-stack`。在旧文档中推荐使用`create-react-app`创建一个新的React项目，但在新文档中推荐使用[Next.js](https://nextjs.org/)或[Remix](https://remix.run/)创建一个新的项目。因此，逐渐向`full-stack`转变是前端发展的一个趋势。

向`full-stack`的转变过程中，有一个很重要的改变就是前端渲染模式的变化。通过`create-react-app`所创建的项目属于单页面应用——`SPA`，其渲染模式为客户端渲染——`CSR`。通过Next.js或Remix创建的项目的渲染模式主要为服务端渲染——`SSR`。那么，二者渲染模式有什么差异？还有其他的渲染模式吗？接下来，让我们对于前端渲染模式及其发展进行分析。

:::info
React is a library. It lets you put components together, but it doesn’t prescribe how to do routing and data fetching. To build an entire app with React, we recommend a full-stack React framework like Next.js or Remix.

\--from react.dev
:::

## 传统服务端渲染

在过去传统开发中，页面渲染任务是由服务端完成的，服务器负责获取数据，拼装页面，客户端仅负责内容显示，使用这种方式的典型技术有`JSP`、`PHP`等等。

很显然，这种方式中，服务器的任务比较繁重，服务器负载相对较大，这样同等情况下一台机器能够同时负载的用户数就变少了。另外开发过程中，由于前后端内容是混在一起的，这就要求开发者要同时掌握前后端知识，要求较高。如果是由前后端工程师合作开发，也容易造成时间冲突，前端页面不出来，后端工程师就要干等着，开发效率不高。于是后来便有了大家熟悉的 Vue.js、React 等 SPA 框架大行其道。

## 客户端渲染

Vue.js、React 这类框架之所以能解决前面提到的问题，主要是因为采用了前后端分离的开发模式，这种方式的特点是页面是由客户端渲染的，应用在客户端首次获取的是空 HTML 文档，浏览器下载并运行 JS，获取数据之后再创建页面，也就是大家熟悉的单页面应用 - SPA。

这种方式，服务端仅提供数据接口，拼装页面的任务交给了客户端，这样充分利用客户端性能，提高了服务器负载能力。开发过程中，前后端分离，前后端工程师各司其职，有效提升开发效率。

但同时，这种渲染模式也存在缺点：用户不得不等待浏览器下载并执行 JS 代码才能看到内容，这跟客户端网速和硬件性能有关，会增加首屏加载时间，影响用户体验。同时，由于页面内容一开始为空，也会导致搜索引擎无法爬取结果，不利于 SEO。

因此，CSR 适合那些不需要 SEO 或者用户访问频率不高的强交互应用，例如：SaaS 系统、后台管理系统、在线文档等。同时，利用浏览器缓存特性，这些应用也可以避免再次下载资源从而提高性能表现。

## 服务端渲染

前面提到 SPA 的 SEO 问题和首屏加载速度问题，有什么好办法可以解决它们呢？其实传统的服务端渲染就行。但是这样一来岂不是开历史倒车，兜兜转转又回去了？

解决方案也不难，只需对传统服务端渲染模式做一些改进：

当用户请求一个 URL 时，先按照传统模式在服务端渲染完整 HTML 页面给浏览器，这样用户立刻可以获得首屏内容；
与此同时，客户端会加载 JS 代码，使前面加载的静态内容重新变成可交互的 SPA，这样后续的页面切换就不再需要刷新和重新获取页面内容了。

为了保证前端程序员能够使用熟悉的方式编写页面，即“同构开发”，服务端渲染时，Next 实际上是在服务器上执行 React，将我们编写的组件渲染为 HTML 并返回客户端。客户端激活时执行的 JS 实际上也是 React，它会重新接管文档，恢复数据和状态，使静态页面变得可交互，这一过程称为“注水(hydration)”。

可以看到，SSR 保留 CSR 优点的同时，还给用户提供了快速加载首屏的能力，这同时也解决了 SEO 问题。

但这种方式也并不完美，因为服务器和浏览器环境有差异，它们不能给开发者提供相同 API，例如，组件在服务端创建时就没有 mounted 钩子，因为根本没有挂载这一步，这导致不少组件库不能在服务端环境正常使用。

又比如，有时候我们的代码只想在客户端运行，如果不小心在服务端执行了就会报错。这些都给开发带来了复杂度和约束性。同样的，由于页面渲染操作发生在服务端，所以服务器负载增加了，只是由于后续 hydration 操作，这个负载增加量被大幅减少了。最后，大家还应该注意的是，SSR 还额外增加了 node.js / serverless 这样的运行时需求，这实际上也是一笔费用。

综上所述，SSR 方式非常灵活，几乎可以适配大多数需求，尤其是一些基于内容的网站：博客，电商，官网等等。

因此 Next 默认渲染模式就是 SSR。

## 静态站点生成

我的博客便是一个静态站点生成的项目，大家可以看一下项目中 packages.json 文件，里面提供了一个 build 命令。这个命令的作用是将写好的动态页面基于数据生成为静态网站，这一过程称为静态内容生成 - Static Site Generate，即 SSG。

为什么要做静态内容生成？答案也很简单，因为如果用户每次访问一个相同的页面都要重新渲染一次相同的内容，无疑是在浪费资源，比如一篇博客或者一个商品详情页，编辑完成之后它们通常不怎么变动，这就没有必要每次重新渲染，如果提前渲染好一个静态页，将会获得最小的服务器负载，最好的性能表现，同时也减少网站的安全风险。

同时，服务器需求也是最少的，我们仅需要静态网站服务即可，这显然可以大幅降低服务器成本。如果我们只是开发一个个人网站或博客，甚至可以通过 github page、vercel 等静态服务零成本建站。

当然 SSG 也存在一些问题，比如网站内容一旦变化，我们就需要重新生成整站，网站内容比较少时这并没有什么问题，如果内容比较庞大，则每次重新生成的时间成本就太大了。因此也就有了增量内容生成 - ISG，当然这就是另一个话题了。

所以，SSG 仅适合内容创建之后不经常变化的网站，例如：个人博客、网站、宣传页等。

## 混合渲染

之前的 Next 版本中，一个应用只能使用一种渲染模式，要么客户端渲染，要么服务端渲染，这种非黑即白的方式不灵活，一些情况下，应用的不同部分用不同的渲染方式更适合。比如电商应用首页经常变动，适合服务端渲染，商品详情页则希望静态化；又比如 CMS 中 admin 部分不需要 SEO，则适合客户端渲染，而所有内容页则仅需要生成一次。

Next 带来一个新的渲染模式，称为混合渲染 - hybrid rendering，顾名思义，这是一种根据不同路由规则使用不同方式渲染的模式。这种方式就可以用来解决前面提出的需求。

所以，如果项目需求使用一种渲染模式不能满足时，就可以考虑混合渲染模式。

## 边缘渲染

过去，SSR 只能运行在 Node.js 环境，但是 Next 提供了跨平台支持，能够同时运行在 Node.js、Deno、Workers 等运行时环境。

这就给用户带来一种全新使用方式：边缘渲染 - edge-side rendering，这种方式能够在 CDN edge worker 环境下直接执行渲染，这样 Next 应用能够运行在离用户更近的环境中，从而降低延迟和服务器花销。

因此，如果大家需要深度优化应用打开和交互速度，例如：实时游戏，交易系统，就可以尝试边缘渲染模式。

## 总结

那么，我们总结一下渲染模式的一些差异：

- 客户端渲染：开发速度快，节约服务器资源；首屏慢，SEO 不友好；
- 服务端渲染：首屏快，SEO 友好，适应性强；开发约束大，服务器费用高；
- 静态站点生成：首屏极快，SEO 友好，服务器成本低；适应性弱，可维护性差；
- 混合渲染：按需渲染，适应性强，可维护性好；稳定性、可用性不好；
- 边缘渲染：性能好，服务器成本低；稳定性、可用性不好。

最后，前端在发展的过程中，渲染模式也在不断的发展，其页面响应速度也越来越快。那么未来的下一代前端框架，一定会在边缘渲染方向，或者在静态站点生成中增量内容生成方向做文章。
