02月04, 2021

Nuxt SSR 性能优化爬坑记录

Nuxt

简介

Nuxt.js 是一个基于 Vue.js 的轻量级应用框架,可用来创建服务端渲染 (SSR) 应用,也可充当静态站点引擎生成静态站点应用,具有优雅的代码结构分层和热加载等特性。

SSR

SSR,即服务器渲染,就是在服务器端将对Vue页面进行渲染生成html文件,将html页面传递给浏览器。

SSR两个优点:

  1. SEO 不同于SPA的HTML只有一个无实际内容的HTML和一个app.js,SSR生成的HTML是有内容的,这让搜索引擎能够索引到页面内容。
  2. 更快内容到达时间 传统的SPA应用是将bundle.js从服务器获取,然后在客户端解析并挂载到dom。而SSR直接将HTML字符串传递给浏览器。大大加快了首屏加载时间。

Nuxt.js的官方网站是这样介绍的:

Nuxt.js 是一个基于 Vue.js 的通用应用框架。 通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染。

Nuxt.js是特点(优点):

  • 基于 Vue.js
  • 自动代码分层
  • 服务端渲染
  • 强大的路由功能,支持异步数据
  • 静态文件服务
  • ES6/ES7 语法支持
  • 打包和压缩 JS 和 CSS
  • HTML头部标签管理
  • 本地开发支持热加载
  • 集成ESLint
  • 支持各种样式预处理器: SASS、LESS、 Stylus等等

性能优化

方案

常规设置

以下设置保证SSR输出的HTML是最小体积。

  • 开启HTML 压缩
  • 抽离JS、CSS资源
  • 关闭gzip压缩

其中,关闭gzip压缩,是降低cpu压力,提高Nuxt性能,通常压缩已经在Nginx开启。

Api接口缓存

将服务端获取的数据,全部缓存到node进程内存中,定时刷新,有效期内请求都通过缓存获取API接口数据,减小数据获取时间;

此种缓存适用于缓存的部分API数据,基本保持不变,变更不频繁,与用户个人数据无关。

示例代码:

import LRU from 'lru-cache'
const CACHED = new LRU({
  max: 100, // 缓存队列长度
  maxAge: 1000 * 60 // 缓存时间
})
export default {
  async asyncData({ app, query }) {
    try {
      let banner, footer
      if (CACHED.has('baseData')) {
        // 存在缓存,使用缓存数据
        let data = CACHED.get('baseData')
        data = JSON.parse(data)
        banner = data.banner
        footer = data.footer
      } else {
        // 获取页面顶部轮播图信息
        const getBanner = () => {
          return app.$axios.$get('zz/zy/banner')
        }
        // 获取底部配置信息
        const getFooter = () => {
          return app.$axios.$get('zz/zy/footer', {
            params: {
              smark: query.smark
            }
          })
        }
        [banner, footer] = await Promise.all([getBanner(), getFooter()])
        // 将数据写入缓存
        CACHED.set('baseData', JSON.stringify({ banner: banner, footer: footer}))
      }
      return {mods: mods, footer: footer}
    } catch (e) {
      console.log('interface timeout or format error => ', e)
      return {}
    }
  }
}

组件缓存

将渲染后的组件DOM结构存入缓存,定时刷新,有效期通过缓存获取组件DOM结构,减小生成DOM结构所需时间;

适用于渲染后结构不变或只有几种变换、并不影响上下文的组件。

示例代码:

nuxt.config.js配置项修改

const LRU = require('lru-cache')
module.exports = {
  render: {
    bundleRenderer: {
      cache: LRU({
        max: 1000, // 缓存队列长度
        maxAge: 1000 * 60 // 缓存1分钟
      })
    }
  }
}

需要做缓存的 vue 组件, 需增加 name 以及 serverCacheKey 字段,以确定缓存的唯一键值。

export default {
  name: 'modal',
  props: ['type'],
  serverCacheKey: props => props.type
}

如果组件依赖于很多的全局状态,或者状态取值非常多,缓存会因频繁被设置而导致溢出,这样的组件做缓存就没有多大意义了;

另外组件缓存,只是缓存了dom结构,如created等钩子中的代码逻辑并不会被缓存,如果其中逻辑会影响上下边变更,是不会再执行的,此种组件也不适合缓存。

页面缓存

当整个页面与用户数据无关,依赖的数据基本不变的情况下,可以对整个页面做缓存,减小页面获取时间;

页面整体缓存前提是在使用Nuxt.js脚手架工具create-nuxt-app初始化项目时,必须选择集成服务器框架,如express、koa,只有这样才具有服务端中间件扩展的功能。

示例代码:

服务端中间件middleware/page-cache.js

const LRU = require('lru-cache')
let cachePage = new LRU({
  max: 100, // 缓存队列长度
  maxAge: 1000 * 60 // 缓存1分钟
})
export default function(req, res, next){
  let url = req._parsedOriginalUrl
  let pathname = url.pathname
  // 通过路由判断,只有首页才进行缓存
  if (['/home'].indexOf(pathname) > -1) {
    const existsHtml = cachePage.get('homeData')
    if (existsHtml) {
      return res.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配置项修改,引入服务端中间件

//针对 / 路由做缓存
serverMiddleware: [
 { path: '/', handler: '~/middleware/page-cache.js' },
]

缓存部分参考资料:Nuxt实现的SSR页面性能优化的进一步探索与实践

虽然已经设置缓存,但是在首次生成缓存需要请求接口,如果在接口返回的50ms内,进来1000个请求,则这1000个请求依旧会被发出,因此需要优化请求队列,并且做好错误处理与重试机制。

本文链接:https://blog.zkit.org/post/nuxt-ssr.html

-- EOF --

Comments