Vue-SSR相信大家都不陌生,与传统 SPA 相比,服务器端渲染 (SSR) 能够具备更好的SEO,方便搜索引擎爬虫抓取工具可以直接查看完全渲染的页面,除此之外,SSR能够在更短的时间内渲染出页面内容,通过在服务端填充数据吐出到客户端的方式,让用户有更好的用户体验。
前言
基于VueSSR的页面优化常有,而针对VueSSR的再优化不常有。前段时间有幸作为宇宙无敌上级特派看门员参加了前端tweb大会,听取了腾讯视频Web高级工程师lucien(段隆贤) 分享了针对SSR场景下的一些优化,由于笔者之前也有在项目中实现SSR渲染,所以也针对Vue-SSR的优化进行了实践和归纳总结,并且在前人的基础上进行了新的优化尝试,当然,不同的项目不同的场景下,优化效果、优化方案可能不尽相同,需要读者们自行选取~(本文将讨论常见的SSR优化方案以及笔者个人的优化尝试)

CSR与SSR的区别
首先,还是要不厌其烦地过一遍CSR和SSR的区别,在理清整个流程后,才能发现性能瓶颈以及关键耗时在哪里。

CSR一般由静态资源服务器(CDN)等直接返回HTML资源,之后浏览器解析HTML加载CSS、JS资源(CSS加载结束后页面会尽快进行首屏渲染FP),JS依赖加载结束后,Vue实例初始化,拉取页面数据,页面渲染(FMP)。

SSR由nodejs服务器来直出页面,请求到达后端后,后端拉取cgi接口数据,根据直出bundle生成render对象,render对象将执行客户端代码构建VDOM,生成HTML string,填充进模板HTML,返回HTML资源,浏览器解析后加载CSS、JS资源,(在CSS加载结束后触发FP和FMP),Vue实例初始化,接管后端直出的HTML,页面可响应。

(以下流程图引自:https://www.jianshu.com/p/10b6074d772c)

时序图

(注:FP即First paint,首屏渲染,可能是没有数据的状态。FMP即First meaningful paint,处于已经渲染数据的状态。可交互:页面数据填充结束且可响应。)

SSR存在的缺陷:

1、对服务器提出更高的要求,生成虚拟DOM如果相对较长的运行和计算耗时;
2、由于cgi拉取和vdom直出后才吐出HTML页面,FMP虽然提前了,但是FP相对延迟了;
3、相比CSR,SSR渲染后,由于仍然需要进行依赖、vue初始化,页面可交互时间并没有较大改善。

常见优化方法

虽然SSR仍有许多不足之处,但是也不是没有改善的空间。

一、缓存优化

1、页面级别缓存:vuessr官网给我们提供了一种方法,如果页面并非千人千面,总是为所有用户渲染相同的内容,我们可以利用名为 micro-caching 的缓存策略,来大幅度提高应用程序处理高流量的能力。这通常在 Nginx 层完成,也可以在 Node.js 中实现。

2、组件级别缓存:通过对组件设置serverCacheKey的方式,如果组件serverCacheKey相同,将复用之前渲染的组件产物,不需要重新渲染。具体是类似这样的:
export default {
name: 'myComponent', // 必填选项
props: ['item'],
serverCacheKey: props => props.item.id,
render (h) {
return h('div', this.item.id)
}
}

3、cgi接口缓存:如果部分cgi接口返回的数据是固定的, 我们可以在node后端拉取cgi的时候,设置cgi缓存,缓存至memcache或其他轻量存储服务,当然,你也需要设置好缓存更新策略。

二、代码实现优化

1、减少组件嵌套层次,优化HTML结构:由于组件最初需要在node后端进行VDOM计算和渲染,优化组件层次结构,减少过深曾经的DOM嵌套,可以减少VDOM计算耗时。

2、减少首页渲染数据量:根据业务调整用户首屏可见的所需渲染的数据,其他数据懒加载或异步加载。

三、资源加载

1、流式传输:vuessr官网给我们介绍了一种方法,render对象会暴露renderToStream方法,把原有的直出结果以流的形式输出,让我们可以更快的响应数据到客户端,能减少首屏渲染时间,更早开始加载页面资源。(流式传输需要在asyncData执行结束后开始,否则没有数据,这意味着流失传输受限于cgi拉取耗时)

2、分块传输:lucien大佬在tweb大会上给我们带来了新的思路,由模板的语法树, 分析代码的上下文,分析数据和模板间的依赖,用异步数据分割模板,分块逐步输出。(相比流式传输,前置位的cgi数据一旦ready,就会渲染输出,而不需要等待所有的gi拉取到后才开始渲染输出,但是该方案改造成本较大)

一张图说明白这两者的区别:

四、改造SSR算法

SSR算法改造:在tweb大会上lucien给我们介绍了一个新的思路,改造直出算法,不用vue-loader而用自研的aga-loader,将vdom渲染转换为字符串模板,具有更高的渲染性能。

性能提高的同时,由于没有完整的组件运行环境,也带来了部分语法上的约束,同时,也不支持vuex。

思考

看到这里,读者们应该对SSR了如如来神掌且熟悉了常见的优化方法,但是回头思考一下,Vue-SSR的优化无非是在 cgi拉取 和 VDOM直出渲染 上下功夫,因为这两者就是node后端最耗时的步骤,其次,由于这种耗时会同步阻塞页面的FP,所以更进一步的方法是流式输出或分块,减少首屏渲染时间。

然而,但是并不是所有的cgi都能缓存,类似拉取用户个人信息的cgi就无法缓存,SSR算法改造成本大,约束也大。再看看流式传输和分块传输,两者虽然都对FP时间优化了,但流式传输受限于cgi拉取时间,分块传输改造成本大。而且两者存在的一个共性问题,那就是可交互时间仍然没有优化。

当然,这里并不是要否定所有的优化方法,而是方法各有优劣,比较优缺点大家才能根据自己的业务需求和优化场景选取合适的优化方法。受流式传输和分块传输的启发,我们能不能在这上面下功夫?在请求到来时,先返回一份完整的HTML空页面,让客户端更快的FP,其次,后端拉取cgi和渲染VDOM 与 前端拉取CSS、JS资源 两者同步进行,之后再吐出直出的HTML string 与 页面store,再次渲染页面,这样的话FP提前了,和CSR的FP时间一毛一样,其次,FMP相比CSR大大提高,更重要的是,由于JS资源的加载让Vue初始化触发的更早,意味着页面可响应时间也会提高。

为了阐明这种区别,我们看一下流程图:



新方案探索与实践

先吐空页面,之后再吐直出后的数据,但是关键是怎么让直出后的数据再渲染上去,同时不要让JS先执行了,导致页面直接变成CSR了。

思考历程:不要让JS执行,等直出数据回来了再执行,这可咋办,笔者最初想实现一个JS加载控制器,不通过script来引入js,而是自己去拉取js代码,eval函数执行,这样js的执行控制权就在自己手上了,但是有几个问题,eval函数解析只是把字符串当js来执行,那错误上报就会出问题,接了sentry错误上报是基于js文件、错误行列来定位的,除此之外,ajax来拉取js代码会不会存在性能问题,和浏览器加载js资源速度上是否存在差异?还有第三方js不能直接ajax拉取,需要设置跨域头。于是笔者开始换一种思路,能不能给每个js文件包裹一层函数,通过setTimeout(fn,0)的方式来延迟调用,但是这又有问题,有多个js文件且文件已经是打包好了的,改了js文件,map映射不就乱了吗?错误上报不就乱了吗?

源码在自己手里,为啥不直接在源码上提供一个调用入口,来触发js执行,最后直出的时候吐出<script>window.render()<script>来控制js执行不就可以了吗?

开始改造

客户端改造:

原有的直出存在entry-client和entry-server两个js文件,分开两个入口各自打包,我们需要改造的是entry-client,让其可控制,开头笔者只是对new App()和mount 包裹了一层函数,但是后来发现,第三方js依赖执行了,其实如果你明白webpack的打包原理,那么require的时候就会触发相应的依赖执行,我们要在entry-client之外再包一层来控制。新增entry-runner文件:
window.__GLOBAL_RENDER__ = function() {
require("./entry-client.js");
console.log("__GLOBAL_RENDER__ ENDING"); // eslint-disable-line
};

以这个为打包入口,加载完js后就不会执行了(当然,还会执行webpack的运行时代码,控制chunk执行的,忽略不计)

改造服务端直出代码:
module.exports = functionhandle(req,res){
res.writeHead(200, {
'Content-Type': 'text/html',
});
res.write(FP_html.replace(/<\/body>[\s\S]*<\/html>/g,''));
// ...other code
}

FP_html是客户端打包的时候生成的index.html,里面已经插入好了css、js依赖,你只需要把尾部body和html的结束标签去掉。

接下来是在直出后吐出直出数据。
constcontext = {
url: req.REQUEST.pathname
};
consthtml = awaitrenderToString(context);
consthtml_render = html.match(/(<div data-server-rendered[\s\S]*<\/div>)[\s\S]*(<script>window.__INITIAL_STATE__[\s\S]*injected -->)/g);
constinnerHTML = RegExp.$1;
conststate = RegExp.$2;
res.write(`
<script>
document.body.innerHTML = \`${innerHTML}\`
</script>
${state}
<script>
setTimeout(()=>{
window.__GLOBAL_RENDER__ && window.__GLOBAL_RENDER__()
},0)
</script>
</body>
</html>
`);
res.end();

通过正则提取出渲染结果html以及store,之后write吐会给前端,innerHTML会覆盖第一次吐给前端的页面中的div#app,接下来state即全局的store初始化,最后setTimeout控制window.__GLOBAL_RENDER__执行即可,因为vue判断是否直出是根据div以及全局的store是否初始化来判断的,所以我们这样做没有问题。其次,为了优先触发一次FMP,我们需要通过setTimeout的方式调用全局渲染方法。

接下来我们来比较一下CSR、SSR以及改造后的效果:

CSR:

SSR:

优化后的SSR:

各项数据对比:
类型\指标
FP
FMP
可交互时间
CSR
0.4s
1.1s
1.2s
SSR
0.7s
0.7s
1s
优化
SSR
0.4s
0.6-
0.7s
0.8s

可以明显的看到,优化后的SSR,FP时间跟CSR一样,让我们的首屏渲染更快了(可优先渲染页面骨架图),其次,FMP时间跟SSR相差不大,最后是可交互时间,由于JS依赖较早开始加载,所以页面直出结束后可马上执行vue初始化逻辑,所以可交互时间缩短到0.8s。

我们找到了一种成本不是很高,不仅优化了FP、FMP时间还优化了可交互时间的方法!

最后

文章看到了这里,相信你对Vue-SSR有了更加深刻的认识和了解,本文比较了CSR和SSR,并总结归纳了Vue-SSR的常见方法,最后在新的方案上进行尝试,达到了一定程度上的优化。优化方案各有优劣,也有成本开销,根据自己业务需求来选择合适的优化方法,才是最有效的。希望本文能给你带来帮助~ 也欢迎讨论其他方法~

基于Vue-SSR优化方案归纳总结相关推荐

  1. Vue SSR技术方案落地实现—构建同构应用

    Vue SSR技术方案落地实现-构建同构应用 一.基本知识扫盲 1.何为服务器端渲染? 1).服务器端渲染:这种技术方案在前端领域处于蛮荒时代就已出现,当时的解决方案主要是后台开发通过模板引擎来设计( ...

  2. React Native 内嵌h5页面(基于vue)的实现方案调研

    React Native 是目前流行的跨平台移动应用开发框架之一.通过采用不同的方法进行混合移动应用开发,它不会生成原生 UI 组件,而是基于 React,React Native 是一个用于构建基于 ...

  3. 【服务端渲染】之 Vue SSR

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:内容较多,建议通过左侧导航栏进行阅读 Vue SSR 基本介绍 Vue SSR 是什么 官方文档:https://ssr.vuejs.org/ V ...

  4. 骨架屏 之 Vue SSR(快捷简易版本解决方案)

    一. 骨架屏简介 简单来说, 骨架屏就是填充了背景等特效的真实页面手稿轮廓图. 它可以是精确/粗略的描述了页面各个元素大小,形状,位置占位的一种页面真实数据渲染加载前的排版. 目的是加载页面过程中给用 ...

  5. 服务器端渲染-Vue SSR搭建

    阅读建议:建议通过左侧导航栏进行阅读 文章简介:本文是Vue.js服务器端渲染的另一种解决方案-SSR(Server-Side Rendering)学习笔记 Vue SSR是什么 官方文档解释:Vue ...

  6. VUE ssr cookie 及 鉴权

    这篇文章基于vue ssr官方教程,如果您没看过,可能觉得内容有点莫名其妙,如果您看过的话,应当知道我在说什么. 由于文档里没有cookie相关的内容,也没有更进一步的讲鉴权,所以我结合网上的一些文章 ...

  7. 史上最全vue优化方案

    博主自家公司的vue项目已经上线将近一年了,最近一直在网上找优化方案,发现许多人写的比较笼统,今天特意整理了一些方案,供自己以后方便查看,也方便大家查看. Vue框架的优点:Vue 框架通过数据双向绑 ...

  8. 一次基于Vue.Js的用户体验优化 (vue drag)

    一.写在前面 半年以前,第一次在项目上实践VueJs,由于在那之前,没有Angular,avalon等框架的实践经验,所以在Vue的使用上,没有给自己总结出更多的经验和体验.随着项目进行和优化改版,无 ...

  9. seo vue 动态路由_基于vue.jsvue-router的动态更新TDK(SEO优化)

    本文基于工作项目开发,做的整理笔记 前几天帮朋友解决这个问题,顺带学习了一下,做个笔记Mark下. 前提条件: 你已经了解并使用vue,能够搭建应用站点. 编码环境: system:OS X EI C ...

最新文章

  1. 如何理解和评价机器学习中的表达能力、训练难度和泛化性能
  2. [Ms SQL] 基本創建、修改與刪除
  3. 一个将当前目录下HEX文件的第一行数据删除的程序...
  4. Android开发之解决NestedScrollView滑动监听兼容低版本的方法
  5. android user版本默认开启调试模式
  6. Meteor——以NodeJS为基础环境,MongoDB为数据环境的全栈开发平台!
  7. Router.use() requires a middleware function but got a Object
  8. 目前看的GNN论文的一些总结
  9. Shell 脚本加密工具-shc
  10. Java练习题11.2 方法的重载
  11. 蒲公英智能云TARA X—永远属于自己的NAS(网络附属存储)
  12. 【web素材】11—15套免费网站后台管理模板
  13. 全面解析python类的绑定方法与非绑定方法
  14. 深入JUnit源码之Runner
  15. 软件架构设计系列总结—写在前面
  16. SAP FICO 内部订单详细解析
  17. uniapp设置的组件样式在H5和APP中生效,在微信小程序中不生效问题解决
  18. 典当行抵押需要什么资料
  19. sp_WhoIsActive
  20. mysql字符集校对和规则

热门文章

  1. 10 个非常有用的 AngularJS 框架
  2. UVA 12166 Equilibrium Mobile
  3. Java数据结构与算法(20) - ch08树
  4. Dotnet 2.0配置系统
  5. CodeForces - 1208F Bits And Pieces(SOSdp+贪心)
  6. 中石油训练赛 - Bouldering(最短路+剪枝)
  7. CodeForces - 1350C Orac and LCM(数论)
  8. PAT (Basic Level) 1044 火星数字(模拟)
  9. c语言的运算符表格,C语言教案(运算符和表格达式).ppt
  10. 数据分析与挖掘理论-数据探索