Nuxt 项目性能优化调研分析
性能优化,这是面试中经常会聊到的话题。我觉得性能优化应该因具体场景而异,因不同项目而异,不同的手段不同的方案并不一定适合所有项目,当然这其中不乏一些普适的方案,比如耳熟能详的文件压缩,文件缓存,CDN,DNS预解析,等等,但是我更希望听到的是因为不同的项目不同的需求,解决不同的问题而采取的不同的优化手段,比如BigPipe,分段输出页面的各个部分,对于SNS网站是非常合适的,减少了用户的等待时间;相对应的还有一个BigRender,这是一个大的延迟加载,360导航首页目前还在使用,京东淘宝首页也是这个思路,对于一些类门户网站非常适用,但是如果你的网页内容不是非常多,就没有必要了
今天要说的是Nuxt。Nuxt是支持VueSSR的一个框架,底层需要运行Node服务。大概描述一下Vue的渲染过程,首先每个组件都会被编译生成一个渲染函数(这部分基本webpack打包已经做掉),然后渲染函数生成虚拟dom,最后虚拟dom通过patch方法将真实dom渲染到页面上。Nuxt其实就是将这部分放到了服务端去做,在服务端拿到渲染页面所需要的html,从而使得html能够直出,而客户端其实还是会运行整个Vue的生命周期,这就带来了一个问题,这部分操作放在了服务端其实是非常耗cpu的,创建组件实例和虚拟DOM节点的开销,无法与纯基于字符串拼接的模版的性能相当,如果是不加优化的Nuxt项目,高并发下是很脆弱的,毕竟Node运行在单线程下,不适合cpu操作密集型的场景
使用Nuxt的项目无非看中了它的两大优点,一是服务端渲染满足SEO的需求,二是首屏直出比SPA快,再加上如果如果公司是Vue系,使用Nuxt就更顺理成章。但是不要忘了性能,高并发下Nuxt性能确实不乐观,我测试了官网的hackernewsdemo项目,2核cpu+4g内存,400并发下它的吞吐量不超过50,就算是最简的Nuxt项目,吞吐量也就300+,这就说明如果项目不做缓存,300+已经是最大的吞吐量了,而最小expressdemo可以轻松到3000,这就决定了高流量项目并不会轻易去使用Nuxt
我们的项目目前其实是一个不加优化的Nuxt项目,因为用户不多,平时并没有什么问题,但是一到展会,就会有不少用户同时访问,反馈页面会很卡。同条件下做了压测后,吞吐量也是50上下,平均响应时长七八秒,所以卡是正常现象
看了一下项目代码,发现了几个问题:
项目没做缓存,所以每次访问都会经历所有Nuxt生命周期,消耗cpu,这点是最致命的
项目打包默认gzip。Nuxt项目打包会默认在服务端开启gzip,因为我们网关层已经做了gzip,所以这里是不必要的,测试了下关掉gzip吞吐量和响应时间都能提高20%左右。具体做法是在nuxt.config.js中配置(还是得看英文文档,会告诉你如何不设置Todisablecompression,usecompressor:false,中文文档当时三月份我写这文的时候还没加这个选项,而目前中文文档也没有翻译这一句2020-07-16)
render:{ compressor:false }
API请求比较乱。很多请求并没有很好地区分客户端和服务端,而是都由服务端去做了,造成服务端压力过大,其实多数和用户有关的请求理应放到客户端。有的接口为了方便,一次性返回了所有内容,也没有做客户端/服务端区分。另外,服务端的接口请求可以并发,用类似Promise.all的形式去控制
SEO。有的内容页面,很长,有五个部分,除了内容外,还有猜你喜欢等其他部分,询问了SEO同事,说这几部分都是需要SEO的,我不是很懂SEO,但是在我看来,ssr只应该渲染首屏内容,而UI在设计的时候应该把主要内容设计到首屏,从而满足SEO
对此我觉得可以从两个方向去优化:
缓存。缓存是最重要的方案,针对Nuxt项目可以做三级缓存,页面缓存、组件缓存以及API缓存。页面缓存是最重量级的缓存方案,能不能做页面缓存可以从以下两个点判断:
同一个URL,对于登录/非登录用户,服务端渲染的内容是相同的(注意是服务端渲染内容,而非前端)
同一个URL,对于不同的登录用户,服务端渲染的内容是相同的,即没有一些个性化的渲染(常见的个性化渲染,比如针对不同用户渲染不同的猜你喜欢内容等)
其实也就是返回的html代码相同就好,主要关注下返回的全局store是否一致,另外也不能做一些服务端才能做的操作,比如set-cookie等
控制好首屏模块个数,对返回的结果进行精简,最小化,保证吐出到浏览器的内容足够小。这就是前面说的并不要对所有模块都做ssr,需要首屏呈现的/需要爬虫爬的,我们直出,其他部分做CSR就行了
而我们的网站大部分页面是满足做页面缓存条件的,测试了下如果做页面缓存,吞吐量能到500+,这个数据这个时候其实是和页面大小有关系了,页面缓存的性能是能满足需求的。而有另一类页面,相同的URL会返回不同的内容,而且整页都是不同内容,它的实现是获取cookie中的不同city-id,渲染不同城市的内容,很显然这部分页面做不了页面缓存了,API缓存和组件缓存理论上都是可以试试的
做缓存优化,至少需要访问一次,第二次才能生效,那么还有另一种情况,对于这样的路由/store/:id,并发打开id0~1000,很显然每个页面都是不一样的店铺数据,并不能命中缓存(可能命中组件缓存,暂时忽略),这个时候只能从Nuxt生命周期上去优化了,那么以上方向的第二点,控制首屏模块个数就能用到了。所以本文一开始我就说,不同的方案是适配不同的场景的,解决不同的问题会采取不同的手段
补充知识:Nuxt实现的SSR页面性能优化的进一步探索与实践
前言
本文之前,先简单介绍以下几个概念:
SSR指服务端渲染,即页面是通过服务端渲染生成后返回给客户端的,SSR主要为了提高页面加载速度,改善用户体验,也可用于SEO搜索引擎优化。
Nuxt.js官方定义:Nuxt.js是一个基于Vue的通用应用框架。通过对客户端/服务端基础架构的抽象组织,Nuxt.js主要关注的是应用的UI渲染。我们的目标是创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码,或者在已有Node.js项目中使用Nuxt.js。
个人理解:Nuxt.js就是预设了开发服务端渲染应用所需要的各种配置,使用Webpack和Node.js进行封装的基于Vue的SSR框架。
背景
我们部门从事的都是面对用户的业务需求开发,面对用户,意味着对页面的体验要求会更高,最直观体验是页面首屏的加载速度,加载速度优化是我们体验优化的长期、重要的一部分;本文的起源正是首屏加载速度优化。
页面加载速度优化的核心包括三点:减少资源文件的请求数量;减小每个资源文件的大小;提高每个资源的加载速度;
诸如合并API访问,压缩混淆文件,支持webp图片,资源cdn缓存等等常用办法,都是以上面三个核心为出发点的;这些常用办法基本都可以通过webpack配置,公司基础服务,代码较小的变更完成。
我们负责的各主流量入口页面,已基本做过以上常用的优化,但由于主入口页面资源量较大的原因,优化后并不能达到预期的效果,我们需要探索其它优化方案。我们快速想到了用SSR的方案进一步解决加载速度问题,从零开始的搭建服务端渲染应用相当复杂,肯定会涉及到服务端的开发,作为独立的前端团队,成本较高昂;我们决定尝试是否能找到一种成本较低的现有SSR框架,以达到目的;
因主入口页面技术栈为vue,方案调研中自然而然的看到了Nuxt.js此种基于Vue的SSR框架;Nuxt.js和项目技术栈匹配度急高,学习成本极低,自然成为我们的第一选择;
我们引入Nuxt.js,最初只是利用了服务端异步获取API接口数据和服务端渲染两项功能,去重构了我们的项目,重构后效果基本达到我们的预期,正常网络状态下,基本可以达到秒开;入口页面,团队是作为一个长期的项目进行不定期优化的,我们逐步围绕Nuxt.js框架,对项目做了进一步优化升级,本文主要介绍我们Nuxt.js页面优化的进一步探索与实践;至于如何搭建初步的Nuxt项目,需要感兴趣的各位自行查看官方文档及自我实践了,本文不做赘述。
探索与实践
我们主要的探索与实践可行方向主要有两个:
一、Nuxt.js特性合理应用
应用到的特性主要包括asyncData异步获取数据、mounted不支持服务端渲染、no-ssr组件不在服务端渲染中呈现;
通过相关特性做到API数据和页面结构合理拆分,首屏所需数据和结构通过服务端获取并渲染,非首屏数据和结构通过客户端获取并渲染。
示例代码:
no-ssr结构拆分
<template> <div> <!--顶部banner--> <banner:banner="banner"/> <!--非首屏所需结构,通过no-ssr组件达到不在服务端渲染目的--> <no-ssr> <!--商品列表--> <prod-list:listData="listData"/> </no-ssr> </div> </template>
API数据拆分
exportdefault{ asyncasyncData({app,query}){ try{ //获取页面顶部轮播图信息 constgetBanner=()=>{ returnapp.$axios.$get('zz/zy/banner') } //获取底部配置信息 constgetFooter=()=>{ returnapp.$axios.$get('zz/zy/footer',{ params:{ smark:query.smark } }) } //并发获取首屏数据,服务端获取 const[banner,footer]=awaitPromise.all([getBanner(),getFooter()]) return{banner:banner,footer:footer} }catch(e){ console.log('interfacetimeoutorformaterror=>',e) return{} } }, mounted(){ //非首屏使用的数据,客户端获取 this.loadListData() }, methods:{ loadListData(){ this.$axios.$get('zz/zy/list').then(()=>{ //数据处理逻辑 }) } } }
二、服务端引入缓存
服务端开发意味着缓存可作为性能优化的最直接法门,Nuxt.js作为一种服务端渲染框架,也不例外;针对不同的页面,不同的数据状态,可主要区分为下面三类缓存:
1、API接口数据缓存
将服务端获取的数据,全部缓存到node进程内存中,定时刷新,有效期内请求都通过缓存获取API接口数据,减小数据获取时间;
此种缓存适用于缓存的部分API数据,基本保持不变,变更不频繁,与用户个人数据无关。
示例代码:
importLRUfrom'lru-cache' constCACHED=newLRU({ max:100,//缓存队列长度 maxAge:1000*60//缓存时间 }) exportdefault{ asyncasyncData({app,query}){ try{ letbanner,footer if(CACHED.has('baseData')){ //存在缓存,使用缓存数据 letdata=CACHED.get('baseData') data=JSON.parse(data) banner=data.banner footer=data.footer }else{ //获取页面顶部轮播图信息 constgetBanner=()=>{ returnapp.$axios.$get('zz/zy/banner') } //获取底部配置信息 constgetFooter=()=>{ returnapp.$axios.$get('zz/zy/footer',{ params:{ smark:query.smark } }) } [banner,footer]=awaitPromise.all([getBanner(),getFooter()]) //将数据写入缓存 CACHED.set('baseData',JSON.stringify({banner:banner,footer:footer})) } return{mods:mods,footer:footer} }catch(e){ console.log('interfacetimeoutorformaterror=>',e) return{} } } }
2、组件级别缓存
将渲染后的组件DOM结构存入缓存,定时刷新,有效期通过缓存获取组件DOM结构,减小生成DOM结构所需时间;
适用于渲染后结构不变或只有几种变换、并不影响上下文的组件。
示例代码:
nuxt.config.js配置项修改
constLRU=require('lru-cache') module.exports={ render:{ bundleRenderer:{ cache:LRU({ max:1000,//缓存队列长度 maxAge:1000*60//缓存1分钟 }) } } }
需要做缓存的vue组件,需增加name以及serverCacheKey字段,以确定缓存的唯一键值。
exportdefault{ name:'zzZyHome', props:['type'], serverCacheKey:props=>props.type }
如果组件依赖于很多的全局状态,或者状态取值非常多,缓存会因频繁被设置而导致溢出,这样的组件做缓存就没有多大意义了;
另外组件缓存,只是缓存了dom结构,如created等钩子中的代码逻辑并不会被缓存,如果其中逻辑会影响上下边变更,是不会再执行的,此种组件也不适合缓存。
3、页面整体缓存
当整个页面与用户数据无关,依赖的数据基本不变的情况下,可以对整个页面做缓存,减小页面获取时间;
页面整体缓存前提是在使用Nuxt.js脚手架工具create-nuxt-app初始化项目时,必须选择集成服务器框架,如express、koa,只有这样才具有服务端中间件扩展的功能。
示例代码:
服务端中间件middleware/page-cache.js
constLRU=require('lru-cache') letcachePage=newLRU({ max:100,//缓存队列长度 maxAge:1000*60//缓存1分钟 }) exportdefaultfunction(req,res,next){ leturl=req._parsedOriginalUrl letpathname=url.pathname //通过路由判断,只有首页才进行缓存 if(['/home'].indexOf(pathname)>-1){ constexistsHtml=cachePage.get('homeData') if(existsHtml){ returnres.end(existsHtml.html,'utf-8') }else{ res.original_end=res.end //重写res.end res.end=function(data){ if(res.statusCode===200){ //设置缓存 cachePage.set('homeData',{html:data}) } //最终返回结果 res.original_end(data,'utf-8') } } } next() }
nuxt.config.js配置项修改,引入服务端中间件
//针对home路由做缓存 serverMiddleware:[ {path:'/home',handler:'~/middleware/page-cache.js'}, ]
总结
本文主要是针对Nuxt.js框架实现的页面,性能优化方案进一步探索和实践的总结,汇总一些思路与方向;期望各位小伙伴在其它SSR相关页面优化过程中,能起到一定的启发作用。
以上这篇Nuxt项目性能优化调研分析就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。