服务端渲染 SSR 技术的探索总结
服务端渲染是什么?
服务端渲染(Server-Side Rendering)是一种用于 Web 开发的技术,旨在当浏览器访问某一路径时,在服务器上完成该路径页面所需数据的获取,拼接成一个完整的 HTML 文档返回给浏览器。
为什么会出现这种方案?
WEB 1.0 时代
服务端渲染的方案其实在 Web 1.0 时代就存在,像以往通过 asp、php 等技术开发 Web ,就是典型的服务端渲染。 整体流程是如下图的:
浏览器拿到的是一个完整的被服务器动态组装出来的 HTML 文本,然后将 HTML 渲染到页面中,过程没有任何 JavaScript 代码的参与,就算把网页的 js 脚本给禁止了,所有内容都会完整地展示出来。
这种模式在以往流行了很长一段时间,但随着市场对 WEB 应用的要求越来越高,渐渐也暴露出了缺点:
- 每次想要改变页面,哪怕只是局部,都需要重新请求一次,重新查一次数据库、组装一次 HTML
- 前后端代码混杂在一起,业务越复杂越难维护
前后端分离
随着 ajax 、NodeJS 的出现,诞生了前端工程师的职业,前端承担更多 WEB 应用的职责,前端团队接管了所有页面渲染的事,后端团队只负责提供所有数据查询与处理的 API。这就是前后端分离的模式:
这就是典型的客户端渲染(CSR)的模式,这种模式的优点就是解决了 WEB 1.0 模式的缺点:
- 每次改变页面内容、跳转页面都不用发起新的 html 请求,而是客户端自己处理并渲染
- 前后端代码分离,分团队管理,利于整体项目的维护和发展
我们常用的 MVVM 框架开发的单页应(SPA),基本都是这种模式。
服务端渲染
随着单页应用的发展,也慢慢暴露出了其缺点:
- SEO 不友好
- 当 JS 脚本臃肿时,首屏渲染时间变慢
为什么客户端渲染不利于 SEO ?
因为绝大部分搜索引擎爬虫只认识网页的 HTML 结构,通过算法解析 HTML 来计算出网页的搜索排名,但是客户端渲染去请求的页面的 HTML 结构很简单,真正给用户呈现的 DOM 是通过 JS 去处理并挂载的。
比如我们用 creat-react-app 跑一个项目:
可以看到服务端返回的 html 内容部分就一个 id 为 root 的空 div 标签,是一个空壳。但是我们看 Elements 下又是有内容的,这部分内容就是 JS 去处理并挂载的。
由于 JS 去处理后挂载的 HTML,是大部分搜索引擎爬虫无法解析的,所以客户端渲染对 SEO 不友好。
为什么首屏渲染时间变慢?
回顾下上述客户端渲染流程: 浏览器请求页面后,需要加载完 js 文件,然后执行 js 代码去获取数据、处理 DOM 并挂载后页面才会渲染出来。
随着业务的复杂,JS 文件会越来越臃肿,需要处理的数据、DOM 就会越来越多,所消耗的时间也越来越长,所以用户需要等待更久的时间才能看到页面。
服务端渲染的再次出现
为了在保留客户端渲染带来的益处的同时又能解决客户端渲染的缺点,大家就想基于 CSR 去找一些解决方案。
思考一下,反正 SEO 和首屏渲染都是作用于对服务器该地址的「第一次访问」,那么我能不能只针对性地优化「第一次访问」就行了?之后的一切操作还是交还给客户端渲染去管理。
那么想要 SEO,是不是页面第一次访问时响应是完整的 HTML 就行了?
由于是完整的 HTML,浏览器拿到 HTML 后就解析渲染出了页面结构,无需等待 js 去请求数据、处理挂载 DOM,是否首屏渲染速度自然就提升了?
其实现如今任何 SSR 方案主要做的两件事就是:
- 页面第一次访问时响应的是完整的 HTML
- 后续操作能继续交给客户端渲染
那么基于客户端渲染的服务端渲染确实解决了客户端渲染的两个痛点,那么这种方案有没有缺点呢? 自然是有的:
- 代码复杂度增加。为了实现服务端渲染,应用代码中需要兼容服务端和客户端两种运行情况(同构),而一部分依赖的外部扩展库却只能在客户端运行,需要对其进行特殊处理,才能在服务器渲染应用程序中运行。
- 需要更多的服务器负载均衡。由于服务器增加了渲染 HTML 的需求,使得原本只需要输出静态资源文件的 nodejs 服务,新增了数据获取的 IO 和渲染 HTML 的 CPU 占用,如果流量突然暴增,有可能导致服务器宕机,因此需要使用响应的缓存策略和准备相应的服务器负载。
- 涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态单页面应用程序 (SPA) 不同,服务器渲染应用程序,需要处于 Node.js server 运行环境。
所以在使用服务端渲染 SSR 之前,需要开发者考虑投入产出比,比如大部分应用系统都不需要 SEO,而且首屏时间并没有非常的慢,如果使用 SSR 反而小题大做了。
概念
同构
所谓同构,就是一套代码需要在服务端执行一遍、在客户端也执行一遍。
举个例子来说,比如 JSX,在服务端执行就是调用 renderToString(jsx)
去生成 html string,在客户端执行就是为生成的 html 绑定事件。
数据注水和脱水
上面有提到过,对于 SSR 来说,数据请求的操作放在了服务端做。当请求到数据后,转化为组件所需的状态( State),然后把 状态 + 模版(JSX) 转化为 HTML 返回给浏览器端。
浏览器拿到 HTML 后会先渲染出 DOM 结构,然后请求并执行 js 文件,此时我们组件的代码也会在客户端被执行一次,也就是同构,我们会再次去初始化路由、状态。
又因为服务端已经请求过数据处理过一次组件状态了,如果不做任何操作,当客户端初始化状态时就拿不到服务端处理过的状态,客户端就会把初始化的状态覆盖给组件用,导致页面会再渲染成丢失了状态的组件。
为了避免这一情况,就出现了数据注水和脱水的方案,一般来说,注水操作就是在返回给客户端的 HTML 中新加个 script 标签,然后把服务端处理过的状态在 script 标签中保存在 window 全局对象里。脱水操作就是在客户端初始化状态前,取出 window 对象中服务端处理后的状态,作为初始状态去初始化组件的状态。
业界方案
两个都是业内成熟的 SSR 框架。
轮子
基于 React 我造了一个 SSR 的轮子: 仓库地址。有心的同学可以从第一条 commit 开始看代码,看完一定更能理解上述概念。
番外 SEO 介绍
title 和 meta description 的真正作用
可能不少人认为 title 标签和 meta description 标签在 SEO 中起着关键作用,其实并不是。现在搜索引擎爬虫大多是读整体 HTML 的内容去分析的,分析内容涵盖了一个网站主要 3 个部分的内容:文本、多媒体(主要是图片)和外部链接,通过这些来判断网站的类型和主题。
那么 title 和 meta description 的真正作用是什么呢? 其实是用户的转化率,比如,同样高排名的网站,title 和 meta description 做得更好的那个,在搜索页的内容就更吸引人,那自然点进网页浏览的用户就更多。
但是,单页应用,页面始终只有一份 title 和 description ,一般是用来描述网站主要的功能的,并且大部分 SEO 后给用户呈现的网址都是网站根路径,若用户想看的内容其实在另一路由下的内容,在只有一份 title 和 description 的情况,就得需要用户点进主页后去找想了解的页面。
若我们每个路由都有对应的 title 和 description 的话,在爬虫分析了整体内容后,会优先展示 title 和 description 更 match 的路由,用户可以直接了当地看到想看内容了。
怎样有多份呢? react-helmet提供了方法,它可以实现根据不同的组件显示来对应不同的网站标题和描述的功能。用法也比较简单,感兴趣可以去看看。
一般如何做好 seo
上文有描述过:**大部分爬虫分析内容涵盖了一个网站主要 3 个部分的内容:文本、多媒体(主要是图片)、外部链接,通过这些来判断网站的类型和主题。**所以我们想要做好 SEO ,也得对症优化这 3 个部分。
文本
对于文本来说,尽量不要抄袭已经存在的文章,以写技术博客为例,东拼西凑抄来的文章排名一般不会高,如果需要引用别人的文章要记得声明出处,不过最好是原创,这样排名效果会比较好。
多媒体
多媒体包含了视频、图片等文件形式,现在比较权威的搜索引擎爬虫比如 Google 做到对图片的分析是基本没有问题的,因此高质量、与网站主题贴合的图片也是加分项。
外部链接
也就是网站中 a 标签的指向,最好也是和当前网站相关的一些链接,更容易让爬虫分析。
预渲染
因为 SSR 架构对代码的开发、维护成本,以及对服务端性能的要求更高,所以应用时,开发人员要仔细斟酌投入产出比。如果需求只是想让 SEO 优化,对项目当下首屏的渲染速度满意的话,我们完全不必应用 SSR 方案,可以使用预渲染技术。
预渲染技术的原理就是:区分出访问我们网页的对象,如果是用户,则直接访问我们项目部署的地址与端口号,按照正常的客户端渲染流程去呈现页面。如果发现访问对象是搜索引擎爬虫,则代理到我们事先开启的一个服务,然后该服务同样访问我们的项目的地址,等待客户端渲染完成后,抓取渲染后的 HTML 返回给爬虫。
爬虫有了完整的 HTML 后,自然可以进行分析了。
首先识别访问对象,可以基于 nginx 去区分代理。然后等待项目渲染完成并抓取 HTML 的操作,业内有现成的工具 prerender。该工具原理大概就是起一个 server,爬虫访问时再起一个简易浏览器去访问真正的项目地址,等待项目客户端渲染完成后,抓取 HTML 返回给爬虫。
预渲染的缺点自然是等待返回结果的时间有点长,毕竟要先起一个浏览器,并且要等待客户端渲染完成,但返回的对象是爬虫,时间稍长些也没关系。
参考
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论