首屏加载速度优化——预渲染

640 阅读4分钟

1 首屏加载速度优化

优化首屏加载速度的方法有很多,比如说之前提到的打包体积的优化。本文在优化打包体积的基础上,使用预渲染进一步优化首屏加载速度。

在实际操作中发现,CDN引入与预渲染在vue.config.js同时配置会冲突。打包部署后的应用会出现页面路由不会跳转的问题,即无论如何点击和切换路由,页面都不会动(有了解的朋友可以来解惑一波)。所以本次对首屏加载速度的优化就仅采用 路由懒加载+预渲染+gzip打包 的方式进行优化。这一优化过程需要服务端进行相应的配合。

预渲染为什么会减少首屏加载的时间

默认情况下,Vue生成的应用是单页面应用,打包后的项目只有一个HTML页面。Vue单页面应用的好处是在刷新/切换页面的时候,只切换相应的组件,减少了切换时的开销。但是随之而来的问题就是首屏加载时间的增加,这是因为我们看到的HTML页面都是JS拼起来的,而浏览器执行JS来生成页面需要消耗较长的时间。

预渲染则是在打包的时候就针对部分路由直接生成HTML文件,这样不需要在浏览器内再进行拼接,减少了时间的消耗。不难发现,这个思想其实结合了单页面应用以及多页面应用的优点,对于一些重要的页面,使用打包后的现成的html页面,可以大大提高首屏加载速度。

2 预渲染

SEO 优化——预渲染(prerender)中的步骤类似,只是这次的场景换到了Vue3,选用prerender-spa-plugin-next包来实现预渲染。

  1. npm i prerender-spa-plugin-next
  2. 在vue.config.js里配置
const plugins = [];
if (process.env.NODE_ENV === "production") {
  const { join } = require("path");
  const PrerenderPlugin = require("prerender-spa-plugin-next");
  plugins.unshift(
    new PrerenderPlugin({
      staticDir: join(__dirname, "dist"),
      routes: ["/", "/robot"], //the page route you want to prerender
      renderAfterDocumentEvent: "render-event",
    })
  );
}

不需要再main.js里配置

3 gzip

plugins: [
  new CompressionWebpackPlugin({
    filename: "[path].gz[query]",
    algorithm: "gzip",
    test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"), // 匹配文件名
    threshold: 10, // 对超过10K的数据进行压缩
    minRatio: 0.8, //极小比
    deleteOriginalAssets: true,
  }),
]

4 路由懒加载

对于上文中配置了需要预渲染的页面,不使用懒加载;对于未配置预渲染的页面,使用懒加载。 使用懒加载后,打包的CSS和JS文件数量会增加,单个文件体积会减小,这样进入不同页面的时候,只需要下载相应的CSS以及JS文件即可,不需要下载一个很大的CSS以及JS文件。

5 CDN引入

CDN引入通常来说是减小打包体积的不错的方法,但是如果我们使用的CDN是从jsdelivr或者bootcdn中引入的,就会存在CDN服务器挂了造成的项目无法正常运行的情况。通常的解决方法是:

  1. 放弃CDN引入
  2. 监听JS错误事件,在监听到错误时立即更换CDN源
  3. 将CDN资源部署在自己的服务器上

6 后端——flask

由于打包后的文件为.gz格式,因此以flask为例进行后端的适配

@app.route('/<path:path>')
def history(path):       
    if path.startswith('css/') or path.startswith('js/')\
            or path.startswith('img/') or path == 'favicon.ico':
        if(os.path.exists('dist/'+path)):
            return app.send_static_file(path)
        else:
            return app.send_static_file(path+'.gz'),200,[("Content-encoding","gzip")]
    else:
        return app.send_static_file('index.html')

与搭配history路由的写法可以结合起来,Flask基础(前端视角),由于之前在打包成.gz文件,但是前端请求的文件不带.gz,因此首先去寻找是否可以找到非压缩的文件,找不到就选择返回压缩的文件,并返回Content-encoding:gzip。

7 刷新页面时,会先闪一下index页面再进入目标的页面

这是由于后端配置history路由的时候,对没有配置过的路由,统一跳转到dist/index.html。但是我们配置过预渲染以后,实际上要跳转到时dist/robot/index.html。因此,对/robot进行配置

@app.route('/robot')
def robot():
    return app.send_static_file('robot/index.html')

8 结果

  1. 懒加载+CDN+gzip image.png
  2. 懒加载+预渲染+gzip image.png 可以发现,使用预渲染的效果会更好。一方面由于两个方案都有gzip的引入,体积大小对于首屏加载速度 的影响已经差别不大了,又因为预渲染是在打包的时候就生成HTML文件,而不是使用js在客户端进行渲染,速度会快很多。

补充:lighthouse里面的几个参数的意义:

FCP(First Contentful Paint)首次内容渲染时间
LCP(Largest Contentful Paint) 最大内容渲染时间
Time to Interactive 可交互时间
Total Blocking Time 累积阻塞时长 
Cumulative Layout Shift 累积布局偏移
Speed Index 速度指数,可以理解为所有元素加载的平均时间

这些参数都越小越好