如果你还不了解服务端渲染,那么我先做个简单的介绍。
早期的 Web 应用,页面内容主要是在服务端生成。例如,在 PHP 中,服务端可以生成内容完整的 HTML 文件,再返回给客户端。
客户端接收到的 HTML 内容,大致如下:
客户端只需要解析完 HTML,就能将内容渲染出来。
这种主要依靠在服务端上执行代码生成页面内容的方式,我们称为服务端渲染(SSR)。
客户端渲染
当前开发 Web 应用,采用更多的渲染方式是客户端渲染(CSR),页面内容主要在客户端生成。客户端接收到的只是一个空的 HTML 文件。内容大致如下:
接收到 HTML 文件后,客户端还需要加载执行完一个 JavaScript 文件,内容才能被渲染出来。
我们可以通过一张动态图来理解这一过程:
我们可以看到,客户端渲染需要依赖 JavaScript 生成页面内容,因此在首屏渲染时可能会遇到较长的白屏时间,并且由于服务器返回的 HTML 内容为空,对站点的 SEO 也不友好。
相反,服务端渲染能返回内容完整的 HTML,提供更快的首屏渲染速度和更好的 SEO 优化。但是,当前开发 Web 应用仍然更倾向于使用客户端渲染,这是为什么?
我们来对比看一下服务端渲染与客户端渲染各自存在的优点与缺点。
服务端渲染 vs 客户端渲染
服务端渲染
优点
- 首屏渲染快: 服务端可以生成页面内容完整的 HTML,客户端直接加载渲染,用户能更快看到内容。
- SEO 友好: 页面内容完整的 HTML,搜索引擎更容易抓取和索引内容。
- 低网络带宽设备的性能好: 服务端渲染能减轻网络带宽低、处理能力弱的设备的渲染负担,缩短加载和显示内容的时间。
缺点
- 服务端压力大: 服务端需要接收请求生成相应的 HTML 页面内容,因此在高并发的情况下,会导致负载过重,影响响应速度和稳定性。
- 交互性差: 不能动态更新页面内容。切换页面往往需要重新加载整个页面的资源,导致页面加载慢,影响用户的交互体验,并且难以实现流畅的切换效果。
- 前后端不分离: 服务端要同时处理前端和后端的逻辑,不能做到分离,后期维护的难度较高。
客户端渲染
优点
- 服务端压力小: 生成页面内容交给了客户端处理,服务端只负责提供数据,减轻了服务器的压力。
- 交互性强: 能动态的更新页面内容,页面切换时不用重新加载整个页面的资源,加载速度快,而且很容易实现流畅的切换效果。
- 前后端分离: 可以做到前后端分离,降低后期维护的难度。
缺点
- 首屏渲染慢: 页面内容需要客户端加载执行完 JavaScript 后才能生成,因此首屏渲染速度较慢,尤其在网络环境差的情况下。
- SEO 不友好: HTML 内容在客户端执行 JavaScript 后生成,低级的搜索引擎爬虫难以抓取页面内容。
- JavaScript 依赖: 用户禁用了 JavaScript 或者加载失败,页面就无法正常显示。
因为,服务端渲染存在交互性差、维护复杂度高的问题难以解决,而客户端渲染的交互性更强、维护复杂度更低,再加上 React 等数据驱动框架的流行,所以客户端渲染的方案逐渐成为了主流。
但是,服务端渲染的优点也是显而易见的,那么有没有办法结合两者的优点,规避两者的缺点呢?
答案是:有。
这就是接下来我们要介绍的:同构渲染。
同构渲染
要理解同构渲染,关键在于理解同构的含义。
在软件开发,特别是 Web 开发中,同构表示同一套代码既在服务端运行,也在客户端运行,两者相互配合共同实现功能,并且要求在不同环境中能保持一致的行为和结果。
同构渲染,就是将服务端渲染与客户端渲染结合的一种方式。其核心思想是:服务端负责生成页面内容,客户端负责处理页面交互。
因此,在同构渲染中:
- 首次请求: 由服务端生成页面内容完整的 HTML,返回给客户端,以加快首屏渲染速度并提高 SEO 效果。
- 后续交互: 由客户端接管,通过执行 JavaScript 来管理后续的页面更新、事件响应等交互操作。
简单点理解就是,同构渲染中只有首屏是由服务端渲染,后续页面都是由客户端渲染。这样就既能保证首屏渲染速度,又能保证交互性。
但问题来了,服务端返回首屏后,客户端要如何才能接管后续的页面渲染呢?
这里就要引入一个新的概念:水合(Hydration)。
如何理解水合?
你可以想一下,服务器返回的 HTML 内容像是一个脱水的三体人(服务端在做渲染时,真的会有脱水这个步骤),没有生机不具备交互的能力。浏览器接收到后,通过使用 JavaScript 为其浸泡,使其恢复生机和交互的能力,这个恢复的过程就叫水合。
水合完成后,客户端就完全接管了页面,可以进行后续的渲染和交互了。
理解水合后,我们再来看一下同构渲染的具体工作流程。
同构渲染的工作流程
同构渲染通常会经历以下几个流程:
- 客户端访问 URL,发送请求到对应的服务器。
- 服务端接收到请求后,根据请求的 URL 匹配相应的处理函数。
- 服务端从数据库或其他外部 API 获取必要的数据,用于后续生成 HTML 页面内容。
- 服务端生成 HTML 页面,并将其返回给客户端。
- 客户端接收到 HTML 后,开始解析渲染页面内容。
- 客户端加载执行 JavaScript,水合页面。
- 客户端接管页面,处理后续的交互。
我们可以通过一张动态图来理解这一过程:
可以看到,在初次请求完成后,客户端就已经能渲染出页面内容了,不过此时的页面还不能进行交互,需要等到客户端加载执行 JavaScript 完成水合后,才能进行交互。
同构渲染与服务端渲染
如今我们在谈论 Web 应用的服务端渲染方案时,一般都是指同构渲染。而早期的服务端渲染,我们称为传统服务端渲染。
与传统服务端渲染不同的是,同构渲染的服务端一般只负责生成 HTML 页面内容,逻辑部分会交给其他服务去处理。
同构渲染存在的问题
虽然同构渲染结合了服务端与客户端渲染的优点,也规避了一些缺点,但是自身仍然存在一些要解决的问题:
- 服务器压力大: 很遗憾同构渲染并没能规避这个问题,即使服务端只负责生成 HTML 页面内容,但在高并发的情况下,依然会负载过重。
- 交互延迟: 虽然能更快的看到页面内容,但需要等待客户端完成水合后才能进行交互,因此交互上可能存在延迟。
- 学习成本高: 同构渲染需要开发人员同时掌握前端与后端的知识,学习成本较高。
以上就是同构渲染的核心内容。在下一篇文章中,我们将通过具体代码来实现一个同构渲染,帮助我们深入的去了解它背后的工作原理。