Vue Node服务端渲染之Nuxt

3,719 阅读6分钟

Nuxt.js 简单介绍

Nuxt.js 是一个基于 Vue.js 的服务端渲染框架。

Vue SSR 的原理图

原理图

Nuxt.js的优势

  • 无需为了路由划分而烦恼,你只需要按照对应的文件夹层级创建 .vue 文件就行
  • 无需考虑数据传输问题,Nuxt 会在模板输出之前异步请求数据(需要引入 axios 库),而且对 vuex 有进一步的封装
  • 内置了 webpack,省去了配置 webpack 的步骤,nuxt 会根据配置打包对应的文件

下图阐述了 Nuxt.js 应用一个完整的服务器请求到渲染(或用户通过 <nuxt-link> 切换路由渲染页面)的流程: 流程

  • Incoming Request:浏览器发出一个请求

  • nuxtServerInit:服务端接收到请求,检查当前有没有nuxtServerInit这个配置项,如果有的化就先执行这个方法,这个方法是用来操作vuex的

  • middleware:这是一个中间件,跟路由相关,这里可以做任何想要的功能

  • validate():验证,配合高级动态路由去做一些验证,比如A页面是否允许跳转到B页面去,如果没有验证通过可以跳转到别的页面之类的校验

  • asyncData()&fetch():获取数据。asyncData()获取的数据是用来渲染vue组件的,fetch通常用来修改vuex的数据

  • Render:渲染

  • Naviage:如果发起了一个非Server 的路由,那么在执行一遍middleware——Render的过程

主要说明

asyncData

asyncData方法是 Nuxt.js 对 Vue 扩展的方法。 会在组件(限于页面组件)每次初始化前被调用的。所以该方法没有办法通过this来引用组件的实例对象。它可以在服务端或路由更新之前被调用。可以利用asyncData方法来获取数据并返回给当前组件。

asyncData (context) {
  return { project: 'nuxt' }
}

asyncData 的参数(context)有哪些?

  • app:app 对象
  • store:存储,可以拿到store里的数据
  • route:路由,可以拿到参数之类的
  • params:url 路径参数,主要获取id
  • query:可以理解为url上问号后面的参数
  • env:运行环境
  • isDev:是否是开发环境
  • isHMR:是否是热更新
  • redirect:重定向
  • error:错误

asyncData 可以做什么?

创建渲染器

Nuxt renderer 使用vue-server-renderer插件创建渲染器并解析 webpack 打好的 bundle 文件

fetch

fetch方法用于渲染页面前填充应用的状态树(store)数据,与asyncData方法类似,不同的是它不会设置组件的数据。

async fetch({ store, params }) {
  const { data } = axios.get('http://abc.com/api/stara')
  store.commit('setStars/set', data)
}

fetch或者asyncData这2个方法在layouts和components中是失效的。

server.js做了哪些事情

每个用户通过浏览器访问 Vue 页面时,都是一个全新的上下文,但在服务端,应用启动后就一直运行着,处理每个用户请求的都是在同一个应用上下文中。为了不串数据,需要为每次 SSR 请求,创建全新的 app, store, router。

我们主要关注最后一部分asyncData部分

首先会根据上下文环境中的url调用 将匹配的component返回

接着遍历每个component,根据component的asyncData配置,执行 promisify()来promise化asyncData方法并将上下文对象赋给asyncData方法

promisify()方法接受两个参数:第一个组件中配置的asyncData()方法;第二个是挂载到新vue实例上的上下文对象

执行后通过方法将得到的数据同步一份给页面中定义的data,asyncData只是在首屏的时候调用一次,后续交互还是交给client处理

window和document对象的使用

在开发nuxt项目的时候,我们难免会使用到window或者document来获取dom元素。如果直接在文件中使用就会报错。这是因为window或者document是浏览器端的东西服务端并没有。 解决方法:我们只需要在使用的地方通过process.browser/process.server来判断

if (process.browser) {
   // 浏览器中运行代码
}

nuxtServerInit

如果在状态树中指定了 nuxtServerInit 方法,Nuxt.js 调用它的时候会将页面的上下文对象作为第 2 个参数传给它(服务端调用时才会酱紫哟)。当我们想将服务端的一些数据传到客户端时,这个方法是灰常好用的。

举个例子,假设我们服务端的会话状态树里可以通过 req.session.user 来访问当前登录的用户。将该登录用户信息传给客户端的状态树,我们只需更新 store/index.js 如下:

actions: {
  nuxtServerInit ({ commit }, { req }) {
    if (req.session.user) {
      commit('user', req.session.user)
    }
  }
}

性能优化

keep-alive

nuxt1.2.0 上添加了这个功能的。在layouts/default.vue:

<template>
  <Nuxt keep-alive/>
</template>

服务端渲染合理应用

应用到的特性主要包括asyncData异步获取数据、mounted不支持服务端渲染、client-only组件不在服务端渲染中呈现;

通过相关特性做到API数据和页面结构合理拆分,首屏所需数据和结构通过服务端获取并渲染,非首屏数据和结构通过客户端获取并渲染。

<template>
  <div>
    <!-- 顶部banner -->
    <banner :banner="banner" />
    <!-- 非首屏所需结构,通过client-only组件达到不在服务端渲染目的-->
    <client-only>
      <!-- 列表 -->
      <prod-list :listData="listData"/>
    </client-only>
  </div>
</template>

组件缓存

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

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

nuxt.config.js配置项修改


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

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

export default {
  name: 'componentName',
  props: ['item'],
  serverCacheKey: props => props.item.id
}
  • 如果组件依赖于很多的全局状态,或者状态取值非常多,缓存会因频繁被设置而导致溢出,这样的组件做缓存就没有多大意义了;

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

页面整体缓存

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

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

页面缓存

对于使用nuxt框架而言,用脚手架初始化完,框架对于vue服务端渲染的res.end()函数做了高度封装, 服务端中间件middleware/page-cache.js

import LRUCache from "lru-cache";
const cache = new LRUCache({
  maxAge: 1000 * 60 * 2, // 有效期2分钟
  max: 100 // 最大缓存数量
});

export default function(req, res, next) {
  // 本地开发环境不做页面缓存
  if (process.env.NODE_ENV !== "development") {
    try {
      const cacheKey = req.url;
      const cacheData = cache.get(cacheKey);
      if (cacheData) {
        return res.end(cacheData, "utf8");
      }
      const originalEnd = res.end;
      res.end = function(data) {
        cache.set(cacheKey, data);
        originalEnd.call(res, ...arguments);
        console.log(data);
      };
    } catch (error) {
      console.log(`page-cache-middleware: ${error}`);
      next();
    }
  }
  next();
}

nuxt.config.js配置项修改,引入服务端中间件

serverMiddleware:[
  { path: '/app', handler: '~/middleware/page-cache' }
]

服务器端渲染中间件(serverMiddleware) vs 中间件(middleware)。不要将它与客户端或SSR中Vue在每条路由之前调用的routes middleware混淆。serverMiddleware只是在vue-server-renderer之前在服务器端运行,可用于服务器特定的任务,如处理API请求或服务资产。

问题

服务端的 axios 不会自动携带 Cookie

在 axios 封装那里新增一个接口来注入 Cookie,然后靠 router-middleware 从 context.headers.cookie 获取并注入

资料