Vue 服务端渲染(SSR)和 NUXT 的介绍

Vue 服务端渲染(SSR)和  NUXT 的介绍

背景

spa单页面seo不友好,因为vue的话是只有一个HTML页面,实现页面的切换是通过监听router进行路由分发,结合ajax加载数据进行渲染的,但是搜索引擎爬虫识别不了js,所以就不会有一个好的排名。但是通常情况下移动端不需要做seo优化。

什么是SSR(Server-Side Rendering服务端渲染)

简单理解是将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。

Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。然而,SSR 也可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。

服务器渲染的 Vue.js 应用程序也可以被认为是"同构"或"通用",因为应用程序的大部分代码都可以在服务器和客户端上运行。所谓同构,通俗的讲,就是一套 Vue 代码在服务端上运行一遍,到达浏览器客户端又运行一遍。在服务端Vue组件渲染为 HTML 字符串(即页面结构),在客户端操作DOM。

打开浏览器的 network,我们看到了初始化渲染的 HTML,并且是我们想要初始化的结构,且完全不依赖于客户端的js文件了。再仔细研究研究,里面有初始化的 DOM 结构,有css,还有一个script标签。script标签里把我们在服务端entry拿到的数据挂载了window上。原来只是一个纯静态的 HTML 页面啊,没有任何的交互逻辑,所以啊,现在知道为什么需要服务端跑一个vue客户端再跑一个vue了,服务端的vue只是混入了个数据渲染了个静态页面,客户端的vue才是去实现交互的!

优缺点

  • 优点
    1. 更好的SEO
      因为SPA页面的内容是通过Ajax获取,而搜索引擎爬取工具并不会等待Ajax异步完成后再抓取页面内容,所以在SPA中是抓取不到页面通过Ajax获取到的内容的;而SSR是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
    2. 更利于首屏渲染
      首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间。
  • 缺点和约束-
    1. 由于没有动态更新,所有的生命周期钩子函数中,只有 beforeCreate 和 created 会在服务器端渲染 (SSR) 过程中被调用。这就是说任何其他生命周期钩子函数中的代码(例如 beforeMount 或 mounted),只会在客户端执行。
    2. 如有在beforeCreat与created钩子中使用第三方的API,需要确保该类API在node端运行时不会出现错误,比如在created钩子中初始化一个数据请求的操作,这是正常并且及其合理的做法。但如果只单纯的使用XHR去操作,那在node端渲染时就出现问题了,所以应该采取axios这种浏览器端与服务器端都支持的第三方库。
    3. 当然对开发来讲我们就不得不多学一些知识来支持服务端渲染
    4. 同时服务端渲染对服务器的压力也是相对较大的,和服务器简单输出静态文件相比,通过node去渲染出页面再传递给客户端显然开销是比较大的,需要注意准备好相应的服务器负载。

SSR原理 (官方渲染步骤)

在实际项目中需要考虑到路由,数据,组件化等等,所以服务端渲染不是只用一个 vue-server-renderer npm包就能轻松搞定的,下面给出一张Vue官方的服务器渲染示意图:

786a415a-5fee-11e6-9c11-45a2cfdf085c.png 流程图大致意思是:

  1. 左边是源代码,app.js被server/client共用,在生产环境下是不用执行的。

  2. 右边是编译后的文件,两份打包后的文件。

  3. 一份运行在服务器端,在服务器上有一个打包的渲染函数负责渲染输出浏览器所需要的html页面

  4. 一份运行在浏览器端,负责跟这份html页面交互,判断当前服务器返回的数据是否是当前URL对应页面的数据。如果不是的话它会请求一次服务器再渲染,如果是的话它就按照正常vue架构接管页面

注意:服务端只是生成前期首屏页面所需的 html ,后期的交互和数据处理还是需要能支持浏览器脚本的 Client Bundle 来完成。

使用nuxt进行官网开发的小结

最初尝试了用vue的服务器端渲染的方案,即用vue-server-render插件然后自己搭建node服务,在尝试过后发现自己要做的事情很多,所以就选用了Nuxt框架,也避免我们踩了很多坑,所以就直接采用nuxt来开发服务器端渲染;但在使用Nuxt框架的过程中也遇到了一些问题,在此记录下来。

nuxt的渲染过程

8daeddd8b5eb4cea9fd3deea15e337e9_tplv-k3u1fbpfcp-watermark.webp 上面的流程,每个具体的用法可以参考nuxt文档

1)nuxtServerInit,这个是在store/index.js中定义的,nuxt会在服务器渲染的时候第一个调用,所以可以把用户信息这类全局的不经常变的信息在这里获取;

2)nuxt中组件可分为页面组件(pages目录下的组件)和非页面组件(components下的各个组件);

asyncData,这个方法的返回值会赋值给data中的对应属性;仅限于页面组件

fetch,这个方法可以操作store,可以用来提前设置好vuex中的状态,还可以在组件中请求相应数据

3)nuxt中组件的生命周期,

在服务器端渲染的时候,只有beforeCreate和created才会被调用;(很明显mounted那些在服务器端也没必须要调用)

在web端渲染的时候,生命周期是正常的

两件事情:

1)第一次打开网页时,是怎么渲染的?

对应流程图中Incoming Request开始的从上到下的流程;

在服务器端渲染的时候,会调用页面组件的asyncData/beforeCreate/created/fetch,以及非页面组件的beforeCreate/created/fetch,渲染完成后,传递给web;

在web端会进行渲染,会调用组件的正常的生命周期:

2)web端渲染后,在网页上点击跳转又是怎么渲染的?

对应流程图中Navigate开始的流程;注意这是在web端渲染的,这时会调用页面组件的asyncData/beforeCreate/created/beforeMount/fetch/mounted,以及非页面组件的beforeCreate/created/beforeMount/fetch/mounted


20210507212428485.png

数据应该在哪获取

我们一般会在created中获取数据,而在nuxt中,对于服务器端渲染的情况这会有两个问题:

1)created会被调用两次,导致获取两次数据;

2)更严重的问题是,nuxt并不会等created中的数据返回后才将渲染结果返回给web,导致虽然去获取数据了,但提交给web的依然是未更新的值,ssr也就没起作用;

很明显,我们就应该在asyncData中获取数据,因为nuxt会等待asyncData中的数据返回并融入到data属性中;

如果数据都在asyncData中获取,而这个方法只能在页面组件中使用,那非页面组件中要获取数据怎么办?

当然是正常获取咯,因为我们在created中获取都是初始数据,这个我们可以在页面组件中通过 asyncData 方法获取到后传入到非页面组件中;页面已经打开后的数据获取,我们正常调用方法获取就行;或者在非页面组件中使用 fetch 方法获取相应的数据。

实用知识点

asyncData 服务端请求异步数据 (pages)

asyncData 主要做服务端数据请求渲染,在它上下文能够解构出axios,route,params...参数,要解构出axios,route,params...参数,要解构出axios,route,params...参数,要解构出axios,还需要做一些额外配置,往下拉有讲到。解构出$axios,就可以做ajax请求,最后把要渲染的数据return出去就行。

export default { 
  async asyncData({$axios,route}){ 
    let data = await $axios('xxx/xxx/xx') 
    return { 
      data 
    } 
  } 
}
复制代码

扩展路由(nuxt.config)

在nuxt默认为约定是路由,就是在pages在创建一个文件,或者一个文件夹就会自动创建对应的路由,无需手动配置什么,方便极了,这里就不多说,这里只要说一下,当我们要对某个地址做一个特殊操作的时候,或者全面接管约定式路由的时候,就需要用扩展路由了。

router: {
    extendRoutes(routes, resolve) {
      routes.push({
        name: 'custom',
        path: '*',
        component: resolve(__dirname, 'pages/404.vue')
      })
    }
  }
复制代码

定制错误页面 (layout)

默认情况下,nuxt提供了一个默认的错误页面,如果你嫌它错的哇,也可以自己定制一个风骚的错误页面,直接下layout目录下定义一个error.vue文件就可以定制自己喜欢的错误页面了,它会代替默认的错误页面,在error.vue的prop有个error属于是包含错误信息的

<template>
  <div> 错误页面{{ error }} </div> 
</template>
<script> 
export default { 
  props:['error'] 
}
</script>
复制代码

数据请求 (nuxt.config)

  • 第一步 npm i -D @nuxtjs/axois
  • 第二步在nuxt.config引入就可以
export default{
  modules: [
   '@nuxtjs/axios'
  ],
}
复制代码

然后重启,就可以在plugin,aysncData...的上下文解构到$axios参数

开启代理

  • 第一步 npm i -D @nuxtjs/proxy
  • 第二步 nuxt.config 下配置
  • @nuxtjs/proxy
  • nuxt.config 下配置 axios和proxy
export default {
  axios:{
     proxy:true
   },
   proxy:{
     'api/':{
       target:'http://localhost:3000'
     }
   }
}
复制代码

axios拦截

在平时开发中请求异步数据,少不了请求前,请求后做一些拦截,在nuxt中也很容易实现,只需定义一个axios拦截plugin

  • 第一步 在plugins目录,起一个性感的插件名,比如叫axios.js
export default function ({ store, redirect, req, router, $axios }) {
  $axios.interceptors.request.use(
    config => {},
    error => {}
  )
  $axios.interceptors.response.use(
    response => {},
    error => {}
  )
}
复制代码
  • 第二步 在nuxt.config中引入插件
export default { 
  plugins: [ 
    { src:'~/plugins/axios', ssr:true // 默认为true,会同时在服务端(asyncData({$axios}))和客户端(this.$axios)同时拦截axios请求,设为false就只会拦截客户端 } 
  ] 
}
复制代码

定制meta(nuxt.config,pages)

定制可以在nuxt.config中定义全局,也可以在pages下定制单独的。

nuxt

export default {
  head: {
    title: 'test',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  }
}
复制代码

pages

export default {
  head:()=>({
    title: 'test',
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  })
}
复制代码



由于时间原因,引用了以下文章,在此,感谢文章作者
juejin.cn/post/684490…
juejin.cn/post/687870…
juejin.cn/post/690146…
blog.csdn.net/weixin_4615…

分类:
前端
标签: