基于vue-ssr的探索

703 阅读4分钟

最近公司项目, 需要需要使用vue服务器端渲染,中间踩了许多坑,项目快要结束了总结一下。

一、 核心概念

vuessr的核心之一就是, 在用户初次访问项目时,先在服务器端获取数据,渲染好首页以html的形式返回给用户, 之后的页面都在客户端进行。所以需要理解哪些些生命周期和动作是在服务器端进行,哪些是在客户端进行,以及如何进行服务器端和客户端数据的同步。 在本文章中,用koa指代项目中的后端逻辑, 用nuxt指代前端逻辑。

二、 项目架构

nuxt文档中vue init nuxt-community/starter-template <project-name>生成的项目是没有node端代码的,只适合比较简单的项目。通过vue init nuxt/koa <project-name>可以生成基于koa的nuxt项目,server/index就是koa的入口文件。实现后端逻辑。此时, nuxt会作为一个koa的中间件存在。可以获取koa的上下文。

我将模板下的server/index.js改写成class形式

// server/index.js
class Server {
  constructor() {
  /** 服务启动时加载其他中间件 **/
    this.app = new Koa()
    // koa-router 
    this.app.use(wx.routes()).use(wx.allowedMethods())
    // 登陆、权限
    this.app.use(authorize)
    // log4 日志
    this.app.on('error', (err, ctx) => {
      log.logError(ctx, err, new Date())
    })
  }

  async start() {
    const nuxt = new Nuxt(config)
    if (config.dev) {
      const builder = new Builder(nuxt)
      await builder.build()
    }
    this.app.use(async (ctx, next) => {
      await next()
      ctx.status = 200 // koa defaults to 404 when it sees that status is unset
      return new Promise((resolve, reject) => {
        ctx.res.on('close', resolve)
        ctx.res.on('finish', resolve)
        // 在这一步中, 将ctx.req注入到nuxt中,可以在nuxt中的asyncData、fetch、的nuxtServerInit方法获取
        nuxt.render(ctx.req, ctx.res, promise => {
          // nuxt.render passes a rejected promise into callback on error.
          promise.then(resolve).catch(reject)
        })
      })
    })

    this.app.listen(port, host)
    console.log('Server listening on ' + host + ':' + port) // eslint-disable-line no-console
  }
}

三、环境变量

nuxt中获取的rocess.env.NODE_ENV 和koa中的rocess.env.NODE_ENV是不一致的

dev 属性的值会被 nuxt 命令 覆盖:

当使用 nuxt 命令时,dev 会被强制设置成 true
当使用 nuxt build, nuxt start 或 nuxt generate 命令时,dev 会被强制设置成 false

实际在启动的时候, koa的环境变量跟随node 而nuxt的是取决于nuxt的指令。 需要在nuxt中区分环境变量的话推荐在nuxt.config.js中通过env属性配置。在启动项目的时候通过cross-env来设置环境

  env: {
    PATH_TYPE: process.env.NODE_ENV,
    serverUrl: process.env.NODE_ENV === 'production' ? 'https://api.xxx.com/api/v1' : 'https://apiceshi.xxx.com/api/v1',
    h5Url: process.env.NODE_ENV === 'production' ? 'https://m.xxx.com' : 'https://mtest.xxx.com'
  }

四, koa中间件

nuxt是作为koa的一个中间件形式存在,并且是最后执行的。利用这一点,可以实现koa路由和nuxt路由的区分。 如果注释掉await nex() 会进入koa的authorize页面,求是不会经过nuxt的。 否则nuxt会覆盖掉这个路由。所以在开发的时候要注意中间件的顺序 举个例子

// koa-router
wx.get('/authorize', async (ctx, next) => {
    ctx.body = 'authorize'
    // await next()
})

五, 跨域

nuxt必定会遇到跨域问题,而且无法通过cookie和session的形式进行会话。 这部分需要api服务器提供支援

六,koa和nuxt的数据传输

既然知道koa的ctx.req会传入nuxt,那么就很好解决了, 在koa的中间件中, 将数据传给ctx.req。 然后就可以在nuxtServerInit中进行初始化。 但是要注意,nuxtServerInit只会触发一次。 这种方式只适合基础数据的初始化

七, nuxt数据获取

页面初次加载, 第一个页面会在服务器端渲染,要注意window对象的问题,例如无法获取cookie、部分UI库无法正常渲染等等问题。在我开发的时候,将接口分为两部分,不需要依赖客户端资源的放在fetch、asyncData中进行,其他的放在mounted进行。fetch中调用可以将数据同步到store中, 而asyncData会将数据合并到当前页面的data中。

八,全局组件,初始化方法

nuxt中没有vue项目中的main和App文件,缺少一个全局的入口作为项目初始化的触发点和全局组件的挂载点。 例如,有一个全局的组件,一个初始化方法需要每个页面都重复一遍也太low了。 这个时候可以在项目最外层包裹一个入口。

在最外层的index中放置全局组件、利用store进行状态控制。写在index/mounted的方法必定会触发一次。

九,nuxt中间件。

nuxt router-middleware 只会在路由变动的时候触发。在服务器端渲染的第一个页面是不会触发的。

十, 404页面

nuxt没有router配置文件。 所以404页面只能通过layouts的error.vue来实现。

十一, UI库定制样式

引入的UI库在需要修改原始样式的时候, 一般都是通过在mian引入一个全局的css文件来覆盖原有样式实现。 在nuxt中,可以在nuxt.config/css中配置