CSR、SSR、Prerender 原理全解密

作前端的同窗们确定或多或少据说过CSRSSRPrerender这些名词,可是大多确定只是停留在据说过,了解过,略懂一点,可是,你真的理解这些技术吗?html

这些名词具体是什么意思呢?前端

为何会产生这种技术,要解决的问题是什么呢?node

每种技术背后的原理又是什么呢?react

从各自的概念和执行流程提及

在了解这些概念以前,咱们要先了解一个熟知的概念,那就是 SPA(Single Page Application),没错,就是你们熟知的单页应用,其实 CSR、SSR、Prerender 都是基于 SPA,关于 SPA 的概念我就很少阐述了。webpack

CSR(Client Side Render)(客户端渲染)

即,渲染过程所有交给浏览器进行处理,服务器不参与任何渲染。web

打包下来页面是这个样子:shell

  
  
  
  

流程:浏览器 --> 服务器 --> index.html(白屏) --> bundle.js --> images --> Renderredux

image-20191231113956671

咱们来使用 create-react-app 来创建一个 web 工程,并在 Chrome 里使用 slow 3G 网络下作个实验:浏览器

Xnip2019-12-29_17-49-12

能够看到,从页面空白到元素绘制用了足足 12 秒左右,这个白屏时间太可怕了,这也就是为何在 Web App 盛行的当下,包体积愈来愈大会致使白屏时间愈来愈长,你们想要优化这个现象的缘由。缓存

Prerender(Pre Render)(预渲染)

即,打包的时候就预先渲染页面,因此在请求到 index.html 就已是渲染过的内容

流程:浏览器 --> 服务器 --> index.html(预渲染的内容) --> Render --> bundle.js + images --> Render

image-20191231113759430

咱们将刚刚的工程加入 prerender-spa-plugin 这个插件,再次运行看看结果

此次打包下来的主页 html 是这个样子的:

  
  
  
  

首页就已经预渲染好了,这时咱们再来运行一次看看:

Xnip2019-12-30_13-54-09

此时能够看到,页面只用了 2 秒就已经渲染出元素,不会形成长时间的白屏问题

SSR(Server Side Render)(服务器渲染)

流程:浏览器 --> 服务器 --> 服务器执行渲染 --> index.html(实时渲染的内容)) --> Render --> bundle.js + images --> Render

image-20191231111204363

可见 SSR 在服务端多作了一些实时渲染的操做,那么咱们此次运行下来回事什么结果呢?

Xnip2019-12-31_11-17-55

能够看出来,SSRPrerender 的效果一致,都能很好的减小白屏时间

总结

从上面的实验,能够看出来,不管是 SSR 仍是 Prerender,咱们要解决的问题主要是白屏时间太长的问题,这两种技术都是为了解决 CSR 的不足之处,那么这两种方案有什么区别?使用场景又有哪些呢?

P.S 其实另外一方面的缘由是,CSRSEO 太不友好了,搜索引擎抓取不到关键信息,只能抓取一个毫无元素的白屏页面,会致使搜索引擎搜索不到你的页面信息进行推荐,SSRPrerender 都能很好的解决这个问题。(吐槽一下:Google 已经实现了抓取基于 SPACSR

Prerender or SSR

在作出选择以前,咱们必需要充分的了解二者的差别。

Prerender 更加通用可是局限性太大

  1. Prerender 原理是在构建阶段就将 html 页面渲染完毕,不会进行二次渲染,也就是说,当初打包时页面是怎么样,那么预渲染就是什么样,若是页面上有数据实时更新,那么浏览器第一次加载的时候只会渲染当时的数据,等到 JS 下载完毕再次渲染的时候才会更新数据更新,会形成数据延迟的错觉。

  2. Prerender 须要预先指定须要渲染的页面,须要手动在 webpack 里设置

        
        
        
        

    因此页面数量很大的状况下,想将每一个页面进行预渲染是很大工做量,并且打包时间会很长,还可能会遗漏

说了那么多利弊,那么,预渲染是怎么作到生成页面的呢?

作过爬虫的同窗确定知道 headless 的概念

Headless Chrome 在 Chrome59 中发布,用于在 headless 环境中运行 Chrome 浏览器,也就是在非 Chrome 环境中运行 Chrome。它将 Chromium 和 Blink 渲染引擎提供的全部现代 Web 平台功能引入命令行。 它有什么用处呢? headless 浏览器是自动测试和服务器环境的绝佳工具,您不须要可见的 UI shell。例如,针对真实的网页进行测试,建立网页的 PDF,或者只是检查浏览器如何呈现 URL。

Prerender 就是利用 Chrome 官方出品的 Puppeteer 工具,对页面进行爬取。它提供了一系列的 API, 能够在无 UI 的状况下调用 Chrome 的功能, 适用于爬虫、自动化处理等各类场景。它很强大,因此很简单就能将运行时的 HTML 打包到文件中。

原理是在 Webpack 构建阶段的最后,在本地启动一个 Puppeteer 的服务,访问配置了预渲染的路由,而后将 Puppeteer 中渲染的页面输出到 HTML 文件中,并创建路由对应的目录。

下面是流程图

image-20191231143313360

SSR 虽好可是要框架支持

目前来讲,主流的框架 ReactVue 都已经支持 SSR,只是配置会繁琐点,有人就会疑惑,框架还要支持 SSR

可事实是,正是由于现代 SPAVirtual DOM 的存在,才能使 SSR 变成现实,可是,SSR 这种理念的实现,并不是易事。

咱们先来看看详细的 SSR 流程图:

image-20191231172059262

能够看出,SSRPrerender 的最大区别就在于,Prerender 是静态的,SSR 是动态的,SSR 会在服务端实时构建出对应的 DOM

这也是 SSR 的难点所在:同构(即服务器和浏览器共同构建)。

何为同构

同构这个概念存在于 VueReact 这些新型的前端框架中,同构其实是客户端渲染和服务器端渲染的一个整合。咱们把页面的展现内容和交互写在一块儿,让代码执行两次。在服务器端执行一次,用于实现服务器端渲染,在客户端再执行一次,用于接管页面交互。

上面咱们说过,SSR 的工程中,React 代码会在客户端和服务器端各执行一次。你可能会想,这没什么问题,都是 JavaScript 代码,既能够在浏览器上运行,又能够在 Node 环境下运行。但事实并不是如此,若是你的 React 代码里,存在直接操做 DOM 的代码,那么就没法实现 SSR 这种技术了,由于在 Node 环境下,是没有 DOM 这个概念存在的,因此这些代码在 Node 环境下是会报错的。

可是就是因为 Virtual DOM 技术的存在,让这一切变成了可能,这里不过多介绍 Virtual DOM,简单来讲,它就是一个普通的 JS 对象,只不过映射了 HTML DOM 的结构,React 在作页面操做时,实际上不是直接操做 DOM,而是操做 Virtual DOM,也就是操做普通的 JavaScript 对象,这就使得 SSR 成为了可能。

咱们能够直接在代码里判断当前的运行环境,若是是浏览器,就能够直接操做 DOM ,若是是服务器,就须要使用 Virtual DOM 生成 HTML 字符串。

到了这里仿佛一切都很简单,一切都这么顺其天然,可是问题又出现了,路由怎么办

浏览器路由和服务器路由彻底是两种不一样的运行机制,SPA 浏览器路由机制能够看这里,其实缘由很简单,在服务器端须要经过请求路径,找到路由组件,而在客户端需经过浏览器中的网址,找到路由组件,是彻底不一样的两套机制,因此这部分代码是确定没法公用。

因此 React 分别为浏览器端和服务器端分别提供了 BrowserRouterStaticRouter 两种路由,经过 BrowserRouter 咱们可以匹配到浏览器即将显示的路由组件,对浏览器来讲,咱们须要把组件转化成 DOM,因此须要咱们使用 ReactDom.render 方法来进行 DOM 的挂载。而 StaticRouter 可以在服务器端匹配到将要显示的组件,对服务器端来讲,咱们要把组件转化成字符串,这时咱们只须要调用 ReactDom 提供的 renderToString 方法,就能够获得 App 组件对应的 HTML 字符串。

那么,如今差很少要完成了吧

尚未!

对于一个 React 应用来讲,路由通常是整个程序的执行入口。在 SSR 中,服务器端的路由和客户端的路由不同,也就意味着服务器端的入口代码和客户端的入口代码是不一样的。而入口则是 Webpack 进行打包完成的。

针对代码运行环境的不一样,要进行有区别的 Webpack 打包,咱们须要在 Webpack 的配置中加入 target: 'node',代表是服务器环境进行打包,除此以外,还有各类各样的配置须要解决。

等等,万一要用到 Redux 来进行状态管理呢

若是要用到 redux 进行全局状态管理,必定要记得写成这种形式:

  
  
  
  

由于服务器端的 Store 是全部用户都要用的,可是不能让全部用户共享 Store ,因此在服务器端渲染中,Store 的建立应该像下面这样,返回一个函数,每一个用户访问的时候,这个函数从新执行,为每一个用户提供一个独立的 Store

最后还有什么注意点吗

因为服务器不存在挂载元素这一辈子命周期,因此例如 ReactcomponentDidMount 或者 VUEmounted 生命周期都不会执行了,因此在服务端利用接口获取数据的时候,不能写入上述的生命周期中。

最后总结

  1. 若是页面无数据,或者是纯静态页面,建议使用 Prerender,这是一种经过预览打包的方式构建页面,也不会增长服务器负担,但其余状况并不推荐。
  2. 当访问量过大时,SSR 的实时构建会加重服务器 CPU 的消耗,需结合其余技术进行处理(例如 CDN,服务器缓存,负载均衡等)
  3. 若是页面数据请求多,又对 SEO 和加载速度有需求的,建议使用 SSR
  4. 对于高操做需求的项目来讲,CSR 可能更加适合,页面显示元素即绑定了操做,而 SSRPrerender 虽然会提早显示页面,但此时页面元素没法操做,仍须要下载完 bundle.js 进行事件绑定才能执行

固然在真正实现 SSR 架构的过程当中,难点有时不是实现的思路,而是细节的处理。好比说如何针对不一样页面设置不一样的 titledescription 来提高 SEO 效果,这时候,咱们其实能够用 react-helmet 这样的工具帮咱们达成目标,这个工具对客户端和服务器端渲染的效果都很棒,值得推荐。还有一些诸如工程目录的设计,404,301 重定向状况的处理等等,不过这些问题,咱们只须要在实践中遇到的时候逐个攻破就能够了。

相关文章
相关标签/搜索