Nuxt.js踩坑实录
文章首发于我的博客:Nuxt.js踩坑实录
前一阵子接触一个新项目,用了vue中的ssr解决方案 — Nuxt.js。也算是小有感受,记录一些经验留后备用。
CSR & SSR
什么是 CSR ?
CSR => client-side-reder,即客户端渲染。具体过程如下:
- 用户请求页面,返回页面。此时页面只是模版页面
- 浏览器解析页面代码,读到js代码时,会根据我们所写的接口去请求数据
- 得到返回数据后使用模版(vue/react/ng/art-template)进行渲染
什么是 SSR ?
SSR => server-side-render,即服务器端渲染。具体过程如下:
- 用户请求页面
- 后端取到准备好的数据,渲染到我们自己写的服务器模版(next/nuxt/ejs)中,准备好html结构与相应数据后返回给浏览器
CSR & SSR 优缺点对比
优点 | 缺点 | |
---|---|---|
CSR | 减轻服务器压力,前后端分离 | 对seo不友(不利于爬虫爬取),首页渲染存在白屏问题 |
SSR | 对seo友好,首页渲染完美无白屏问题 | 对服务器性能有一定要求,不利于前后端分离 |
其实在真正开发中通常是 csr 与 ssr 相结合使用,前端使用cdn缓存,后端使用nginx缓存。这样是最优的解决方案。上两张图大家对比理解:
csr ssrNuxt.js
什么是 Nuxt ?
Nuxt.js 是一个基于 Vue.js 的通用应用框架。通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI渲染。
Nuxt 流程
Nuxt流程图nuxtServerInit
请求先到达 nuxtServerInit 方法,图中也表明了适用场景是对 store 的 action 操作即(vuex):
middleware
下来请求到达 middleware 属性,虽然这里叫做中间件属性,但还是做着同中间件一样的事。官方给出的定义:
中间件允许您定义一个自定义函数运行在一个页面或一组页面渲染之前。
也就是说,定义 middleware 属性后你可以在 匹配布局(layout 组件)前执行某种操作,也可以在解析完 layout 之后,解析 page 组件前 执行某种操作。
validate
下来请求到达 validate 方法,在这里可以对 page 组件或者 page 中的子组件 component 进行动态路由对应的页面组件中动态路由参数的有效性。
asyncData & fetch
接下来达到 asyncData & fetch 方法,asyncData() 适用于在渲染组件前获取异步数据,fetch() 适用于在渲染页面前填充 vuex 中维护的数据。
render
最后进行渲染。将渲染后的页面返回给浏览器,用户在页面进行操作,如果再次请求新的页面,此时只会回到生命周期中的 middlerware 中,而非 nuxtServerInit ,所以如果不同页面间需要操作相同的数据请用 vuex 来维护。
Nuxt 笔记
Nuxt 项目结构
通过
npm install npx
npx create-nuxt-app xxx
生成如上结构,布局组件(layout)用来存放页面整体布局,在 layout 中我们可以放入一些每个页面都会以用到的组件,比如 header & footer。当然如果你不想使用已生成的 layout 组件,你可以重新创建一个,比如 blank.vue 一般不需要引入 header&footer 的页面可以使用 blank.vue 这个 layout 组件。代码如下:
layout: 'blank'
在 nuxt 中引入 layout 不用写路径,nuxt 已经帮我们做了映射,同理在 nuxt 中不用写路由,pages 中文件名映射为了路由。
nuxt 中的钩子函数
nuxt 中提供很多钩子函数,参考文档:nuxt hooks
这里我们主要来看 vue 组件生命周期中钩子函数,服务器端的 vue 组件也是有生命周期的,只不过只有 beforeCreate 和 created 俩个。也就是说,我们把服务器端创建的 .vue 文件全部理解成组件,在服务器端环境(node)通过 beforeCreate 和 created 这俩个生命周期节点后服务器端 vue 组件生命周期结束。返回页面给浏览器,在客户端环境(v8)中这个 vue 组件实例创建后会在客户端再次拥有生命周期,此时生命周期中有 mounted 等钩子函数。
需要特别注意的是 nuxt 中没有 mounted 钩子函数也没有组件实例,只有 beforeCreate/created 钩子与 context 对象。
asyncData() 传递服务器数据至客户端
在 asyncData() 中可以处理请求得来的数据,通过 return 将处理后的数据返回给当前 vue 组件的 data 。再次强调这里不能使用 this ,因为没有组件实例,asyncData() 默认的参数是 ctx 即 content 对象。
对于打开网页要立即显示的内容,如首页中的 geo 组件(显示当前位置)来说有俩种方式实现,如下:
- ssr
- nuxtServerInit 方法
- middleware 属性
- vue 组件 mounted 函数发送请求
no-ssr 即 mounted 函数方式实现相信大家都很熟悉。主要来说说 ssr 对应的俩种实现方式。
nuxtServerInit() 前面也说过,搭配 vuex 使用,所以顾名思义,在 nuxtServerInit 函数中准备好首页 geo 组件需要的数据存入 vuex,这样 vuex 实例化后的 store 会贯穿整个服务器端与客户端的生命周期。可能有的同学会问这是怎么传过去的?文末会截图说明。所以在首页返回客户端时数据存在 store 实例中可直接获取即可。获取方式如下:
- 服务器端:
ctx.store.state.xxx.xxx.xxx
// eg: ctx.store.state.home.position.city
- 客户端:
this.$store.state.xxx.xxx.xxx
// eg: this.$store.state.home.position.city
middleware 属性是在 vue 组件中直接定义即可,如下:
middleware: async (ctx) => {
let {status, data: {province, city}} = await ctx.$axios.get('/geo/getPosition')
...
}
问题来了,如何将获取的 data 赋值给组件中的 data 呢?上面在 nuxt流程图 部分我们分析过,渲染前的最后一步是 asyncData&fetch ,这里我们还是需要用 asyncData 方法把 data 给组件 return 即可。具体做法见官方给出的 issue & 例子:
解答上面遗留的问题,ssr 生成的页面如何把 vuex 实例后的 store 传给客户端?
其实在服务器返回页面给浏览器时会传一个名为 __NUXT__ 的对象挂载在 window 上,这里面存储了 store 。如下:
如图可以找到 $store.state.home.position.city 这个数据。
参考文章:
The Benefits of Server Side Rendering Over Client Side Rendering