“服务端渲染”吊打“客户端渲染”的那些事(The Benefits of Server Side Rendering Over Client Side Rendering)

2,718 阅读9分钟

The Benefits of Server Side Rendering Over Client Side Rendering

(先交代一下背景),我们walmart(沃尔玛)主站的(大)多数页面,使用的是服务端渲染(以下用简称SSR代替),只有极个别页面使用的是客户端渲染。

我们之所以使用服务端渲染,正是基于以下两个原因:

  • 在用户购物的过程中,能够让用户享受到页面性能(的)提升所带来的用户体验

  • 符合(consistent)我们对页面SEO性能优化的要求

正是因为SSR拥有这些优点,这才让我们下定决心(对页面)使用服务端渲染。(不过,说实话),当我们(真的)把技术栈切到React和Nodejs的时候,(吃惊的发现),SSR性能的优化工作(其实)也花了我们很多时间和精力。(为啥呢)?这是因为SSR性能优化的一部分工作是针对首屏渲染的优化,(忘记说了),首屏渲染(above-the-fold render)也是我们衡量性能的重要指标之一。(顺道安利一下)我们自己的开源框架Electrode framework,该框架内置了多个有助于SSR性能提升的独立模块,(如果没记错的话),我之前就写过有关于使用这些模块所带来性能提升的介绍

Eletrode框架(旨在解决SSR性能优化问题)一经发布,不久我就收到了许多有关于使用SSR(究竟)能带来(哪些)好处的提问以及相关的评论。你们现在看的这篇文章,主要讲的是使用SSR(究竟)能带来(哪些)好处,至于SEO优化(究竟)能带来(哪些)好处等话题,(我觉得)Andrew FarmerPatrick Hund他俩的文章已经讲得很全面啦,(我这里将不再赘述)。

理论上的性能优点

接下来,我将会用最直观(super simple)的时序图(timeline diagram)来阐述SSR以及CSR之间的区别。

SSR

CSR

从图中可以看出,(这两种渲染方式的)区别主要在于出现首屏渲染的时机。对于SSR来说,服务器返回的是(结构相对完整的)HTML文件,(通过解析HTML文件),浏览器就能渲染出页面。而对CSR来说,浏览器拿到的只是包含JavaScript代码的HTML文件,(换句话,在浏览器渲开始渲染出页面之前,需要动态创建HTML标签)。这也就意味着,SSR可以让浏览器在边下载JavaScript文件的同时边渲染HTML页面,换句话说,浏览器再也不需要等到所有的JavaScript文件下载并执行完之后才去渲染页面啦。(译者注:上述方式属于增量式构建),从SSR以及CSR的时序图里,我们可以发现,这两种渲染方式还是有蛮多共同点的:

  • 都需要下载React的
  • 都需要经历虚拟DOM构建过程
  • 都需要(给页面元素)绑定事件来增强页面的可交互性

不过对于使用SSR方式渲染出的HTML页面来说,用户是可以在这些操作(指的是下载React、构建虚拟DOM、绑定事件)完成之前就能看到页面。再反观使用CSR方式渲染出的HTML页面,你必须等到上面的这些操作(指的是下载React、构建虚拟DOM、绑定事件)都完成,virtual-dom转换成(浏览器)页面上的真实dom之后,用户才能看到页面。

使用SSR渲染的另一大优势(Another Bonus):(熟悉浏览器渲染机制的人都应该知道),使用CSR渲染的话,页面很容易白屏。相反,如果你使用SSR渲染的话,白屏就不(那么)容易出现啦。尽管大家都知道,使用CSR(在很大程度上)就意味着页面白屏,不过大多数人还是会使用下面的这种方式来规避(白屏)风险(在服务器返回所有数据之前,给页面添加loading图,然后在所有数据到达之后,把loading图撤掉)。(译者注:用砖业术语是酱紫描述的,在数据到达之前,让页面开始转“菊花”,数据到达之后,移除“菊花”图)

前方高能预警(there are a few caveats),请所有(使用SSR方式)的小伙伴提前做好战斗准备:

  • 虽说使用SSR方式渲染,不但可以让浏览器更早的渲染出HTML页面,而且还能让用户更快的看到HTML页面。不过美中不足的是,在React没有执行之前,这些页面是不存在交互这一说的,甚至是包括像用户(快速)点击按钮等类似(这样简单)的交互都不能完成。
  • (熟悉HTTP协议的人都应该知道),在使用SSR方式渲染HTML页面的过程中,浏览器获取第一个字节的时间(Time To First Byte)要长于用CSR渲染HTML页面所获取的时间,(为啥呢)?这是因为在你使用SSR方式渲染页面的过程中,你服务器需要花更多的时间来渲染出(浏览器所需要的)HTML结构,(最后才将渲染好的HTML结构作为响应返回),而不像CSR那样,服务器只需要返回字节相对较少的Json数据(relatively empty respons)。

  • (通过对比SSR、CSR这两种渲染方式,你会发现),在使用SSR方式渲染HTML页面的过程中,服务器的吞吐量会明显少于用CSR渲染HTML页面时服务器的吞吐量。尤其是当你在服务端使用react的时候,(你会发现,是否使用react的服务端渲染特性,服务器吞吐量往往也是我们考虑的因素),这是因为react对服务器吞吐量的影响太大啦(the throughput impact is extremely large)。ReactDOMServer.renderToString具有以下特点:

(换句话说),在ReactDOMServer.renderToString没有执行完之前,服务器是(绝)不可能处理其它请求的。(啥?你说我讲的太抽象啦,完全听不懂),(那好吧,不妨)让我们做个假设(Let’s say ),在使用SSR渲染HTML页面的过程中,执行ReactDOMServer.renderToString就花了500ms,这意味你现在每秒最多只能处理两个请求。(如果有同学对这方面感兴趣的话,可以重点关注一下)

真实案例(Real Use Case)

(接下来,我将会带着大家来亲身感受一下,我们walmart主站是如何在生产环境实践SSR以及CSR的),以下是我们针对SSR、CSR这两种渲染方式所做的一些对比。

CSR vs SSR

为了(更好的)对比SSR、CSR这两种渲染方式,我们分别拿出了(walmart主站的)home、category以及search三个模块来进行对比。(忘记说了),当前的这张图片以及接下来的图片都是我们在页面进行渲染的过程中,使用chrome(浏览器自带的Screenshots工具)所抓取的快照。(译者注:此处应该有掌声),通过查看图片,你会发现使用SSR的话,HTML页面渲染更快,相反使用CSR的话,HTML页面在加载过程中会出现白屏。虽说绝大多数应用还是可以通过(显式地使用)loading图的方式来解决页面白屏,不过我们的做法是酱紫的:在常规情况下,我们会使用SSR方式,(不过一旦 生产环境出现紧急情况),需要强制切到CSR的情况下,页面还是会出现白屏。请注意,虽说上面的图片只是(对HTML页面渲染情况的)一次快照,(不过大家应该不用担心),因为我们每天都会定期在不同的时间点、不同的生产环境来跑我们的机器,所以说我们得到的个体样本数据非常全(vary-but),(全到啥子程度呢)?你都可以从这些数据中看出app接下来的性能走势啦。

SSR & CSR

我们可以看到,(在分别使用SSR、CSR这两种渲染方式的情况下),服务器会针对home、category以及search页面返回相对应的响应头数据。不过我会绿色bar忽略掉,这是因为在网络拓扑图(network graph)当中,(我们发现),相比绿色bar位置之外的数据来说,绿色bar所反映出的数据更具有相对性(more relative)。(在这些数据当中),我比较关注的是文件大小以及TTFB。既然服务器会针对不同的页面返回相对应的HTML,(那么你就有可能注意到),使用SSR方式渲染出的HTML体积总是比用CSR方式大。另外一点就是关于服务器的响应速度,(经过我们测试发现),使用CSR方式,能够让服务器更快地响应页面请求(由于其它原因,在请求home page页面的过程中,服务器响应的速度反而变慢啦)。

我不能完全保证上述结论的正确性,这是因为上述结论掺杂了很多不稳定的因素比如网络是否延迟,服务器是否正常,访问者的地理位置以及其它因素。所以希望大家不要把上述结论理解为真理(scientific fact),更多的是把它理解为对SSR以及CSR这两种渲染方式趋势的一种解读。

Electrode

当我们分别对CSR、SSR这两种渲染方式进行A/B测试时,发现(我们看到的)结果和之前预期的一样,另外我们的统计数据显示,渲染的越早,用户的参与度就会越高。

正是基于以上原因,我们才决定把开源electrode平台的关注点放在SSR上。electrode默认开启SSR,另外我们有一些模块是围绕优化SSR展开的。在另外一篇文章中,我将会为大家展示,如何使用这两个模块,将RenderToString()的时间缩减至30%

非常感谢MayakumarCaoyang帮我review并润色这篇文章。