Nuxt.js实战和配置详解


Posted in Javascript onAugust 05, 2019

前段时间刚好公司有项目使用了Nuxt.js来搭建,而刚好在公司内部做了个分享,稍微再整理一下发出来。本文比较适合初用Nuxt.js的同学,主要讲下搭建过程中做的一些配置。建议初次使用Nuxt.js的同学先过一遍官方文档,再回头看下我这篇文章。

一、为什么要用Nuxt.js

原因其实不用多说,就是利用Nuxt.js的服务端渲染能力来解决Vue项目的SEO问题。

二、Nuxt.js和纯Vue项目的简单对比

 1. build后目标产物不同

vue: dist

nuxt: .nuxt

2. 网页渲染流程

vue: 客户端渲染,先下载js后,通过ajax来渲染页面;

nuxt: 服务端渲染,可以做到服务端拼接好html后直接返回,首屏可以做到无需发起ajax请求;

3. 部署流程

vue: 只需部署dist目录到服务器,没有服务端,需要用nginx等做Web服务器;

nuxt: 需要部署几乎所有文件到服务器(除node_modules,.git),自带服务端,需要pm2管理(部署时需要reload pm2),若要求用域名,则需要nginx做代理。

4. 项目入口

vue: /src/main.js ,在main.js可以做一些全局注册的初始化工作; nuxt: 没有main.js入口文件,项目初始化的操作需要通过 nuxt.config.js 进行配置指定。

三、从零搭建一个Nuxt.js项目并配置 新建一个项目

直接使用脚手架进行安装:

npx create-nuxt-app <项目名>

Nuxt.js实战和配置详解

大概选上面这些选项。

值得一说的是,关于 Choose custom server framework (选择服务端框架),可以根据你的业务情况选择一个服务端框架,常见的就是Express、Koa,默认是None,即Nuxt默认服务器,我这里选了 Express

  • 选择默认的Nuxt服务器,不会生成 server 文件夹,所有服务端渲染的操作都是Nuxt帮你完成,无需关心服务端的细节,开发体验更接近Vue项目,缺点是无法做一些服务端定制化的操作。
  • 选择其他的服务端框架,比如 Express ,会生成 server 文件夹,帮你搭建一个基本的Node服务端环境,可以在里面做一些node端的操作。比如我公司业务需要(解析protobuf)使用了 Express ,对真正的服务端api做一层转发,在node端解析protobuf后,返回json数据给客户端。

还有 Choose Nuxt.js modules (选择nuxt.js的模块),可以选 axiosPWA ,如果选了axios,则会帮你在nuxt实例下注册 $axios ,让你可以在.vue文件中直接 this.$axios 发起请求。

开启eslint检查

nuxt.config.js 的build属性下添加:

build: {
  extend (config, ctx) {
   // Run ESLint on save
   if (ctx.isDev && ctx.isClient) {
    config.module.rules.push({
     enforce: 'pre',
     test: /\.(js|vue)$/,
     loader: 'eslint-loader',
     exclude: /(node_modules)/
    })
   }
  }
 }

这样开发时保存文件就可以检查语法了。nuxt默认使用的规则是 @nuxtjs (底层来自eslint-config-standard ),规则配置在 /.eslintrc.js :

module.exports = {
 root: true,
 env: {
  browser: true,
  node: true
 },
 parserOptions: {
  parser: 'babel-eslint'
 },
 extends: [
  '@nuxtjs', // 该规则对应这个依赖: @nuxtjs/eslint-config
  'plugin:nuxt/recommended'
 ],
 // add your custom rules here
 rules: {
  'nuxt/no-cjs-in-config': 'off'
 }
}

如果不习惯用 standard 规则的团队可以将 @nuxtjs 改成其他的。

使用dotenv和@nuxtjs/dotenv统一管理环境变量

在node端,我们喜欢使用 dotenv 来管理项目中的环境变量,把所有环境变量都放在根目录下的 .env 中。

安装:

npm i dotenv

使用: 在根目录下新建一个 .env 文件,并写上需要管理的环境变量,比如服务端地址 APIHOST :

APIHOST=http://your_server.com/api

/server/index.js 中使用(该文件是选Express服务端框架自动生成的):

require('dotenv').config()

// 通过process.env即可使用
console.log(process.env.APIHOST) // http://your_server.com/api

此时我们只是让服务端可以使用 .env 的文件而已,Nuxt客户端并不能使用 .env ,按Nuxt.js文档所说,可以将客户端的环境变量放置在 nuxt.config.js 中:

module.exports = {
 env: {
  baseUrl: process.env.BASE_URL || 'http://localhost:3000'
 }
}

但如果node端和客户端需要使用同一个环境变量时(后面讲到API鉴权时会使用同一个SECRET变量),就需要同时在 nuxt.config.js.env 维护这个字段,比较麻烦,我们更希望环境变量只需要在一个地方维护,所以为了解决这个问题,我找到了 @nuxtjs/dotenv 这个依赖,它使得nuxt的客户端也可以直接使用 .env ,达到了我们的预期。

安装:

npm i @nuxtjs/dotenv

客户端也是通过 process.env.XXX 来使用,不再举例啦。

这样,我们通过 dotenv@nuxtjs/dotenv 这两个包,就可以统一管理开发环境中的变量啦。

另外, @nuxtjs/dotenv 允许打包时指定其他的env文件。比如,开发时我们使用的是 .env ,但我们打包的线上版本想用其他的环境变量,此时可以指定build时用另一份文件如 /.env.prod ,只需在 nuxt.config.js 指定:

module.exports = {
  modules: [
  ['@nuxtjs/dotenv', { filename: '.env.prod' }] // 指定打包时使用的dotenv
 ],
}

@nuxtjs/toast模块

toast可以说是很常用的功能,一般的UI框架都会有这个功能。但如果你的站点没有使用UI框架,而alert又太丑,不妨引入该模块:

npm install @nuxtjs/toast

然后在 nuxt.config.js 中引入

module.exports = {
  modules: [
  '@nuxtjs/toast',
  ['@nuxtjs/dotenv', { filename: '.env.prod' }] // 指定打包时使用的dotenv
 ],
 toast: {// toast模块的配置
  position: 'top-center', 
  duration: 2000
 }
}

这样,nuxt就会在全局注册 $toast 方法供你使用,非常方便:

this.$toast.error('服务器开小差啦~~')
this.$toast.error('请求成功~~')

API鉴权

对于某些敏感的服务,我们可能需要对API进行鉴权,防止被人轻易盗用我们node端的API,因此我们需要做一个API的鉴权机制。常见的方案有jwt,可以参考一下阮老师的介绍: 《JSON Web Token 入门教程》 。如果场景比较简单,可以自行设计一下,这里提供一个思路:

  1. 客户端和node端在环境变量中声明一个秘钥:SECRET=xxxx,注意这个是保密的;
  2. 客户端发起请求时,将当前时间戳(timestamp)和 SECRET 通过某种算法,生成一个 signature ,请求时带上 timestampsignature
  3. node接收到请求,获得 timestampsignature ,将 timestamp 和秘钥用同样的算法再生成一次签名 _signature
  4. 对比客户端请求的 signature 和node用同样的算法生成的 _signature ,如果一致就表示通过,否则鉴权失败。

具体的步骤:

客户端对axios进行一层封装:

import axios from 'axios'
import sha256 from 'crypto-js/sha256'
import Base64 from 'crypto-js/enc-base64'
// 加密算法,需安装crypto-js
function crypto (str) {
 const _sign = sha256(str)
 return encodeURIComponent(Base64.stringify(_sign))
}

const SECRET = process.env.SECRET

const options = {
 headers: { 'X-Requested-With': 'XMLHttpRequest' },
 timeout: 30000,
 baseURL: '/api'
}

// The server-side needs a full url to works
if (process.server) {
 options.baseURL = `http://${process.env.HOST || 'localhost'}:${process.env.PORT || 3000}/api`
 options.withCredentials = true
}

const instance = axios.create(options)
// 对axios的每一个请求都做一个处理,携带上签名和timestamp
instance.interceptors.request.use(
 config => {
  const timestamp = new Date().getTime()
  const param = `timestamp=${timestamp}&secret=${SECRET}`
  const sign = crypto(param)
  config.params = Object.assign({}, config.params, { timestamp, sign })
  return config
 }
)

export default instance

接着,在server端写一个鉴权的中间件, /server/middleware/verify.js

const sha256 = require('crypto-js/sha256')
const Base64 = require('crypto-js/enc-base64')

function crypto (str) {
 const _sign = sha256(str)
 return encodeURIComponent(Base64.stringify(_sign))
}
// 使用和客户端相同的一个秘钥
const SECRET = process.env.SECRET

function verifyMiddleware (req, res, next) {
 const { sign, timestamp } = req.query
 // 加密算法与请求时的一致
 const _sign = crypto(`timestamp=${timestamp}&secret=${SECRET}`)
 if (_sign === sign) {
  next()
 } else {
  res.status(401).send({
   message: 'invalid token'
  })
 }
}

module.exports = { verifyMiddleware }

最后,在需要鉴权的路由中引用这个中间件, /server/index.js

const { Router } = require('express')
const { verifyMiddleware } = require('../middleware/verify.js')
const router = Router()

// 在需要鉴权的路由加上
router.get('/test', verifyMiddleware, function (req, res, next) {
  res.json({name: 'test'})
})

静态文件的处理

根目录下有个 /static 文件夹,我们希望这里面的文件可以直接通过url访问,需要在 /server/index.js 中加入一句:

const express = require('express')
const app = express()

app.use('/static', express.static('static'))

四、Nuxt开发相关 生命周期

Nuxt扩展了Vue的生命周期,大概如下:

export default {
 middleware () {}, //服务端
 validate () {}, // 服务端
 asyncData () {}, //服务端
 fetch () {}, // store数据加载
 beforeCreate () { // 服务端和客户端都会执行},
 created () { // 服务端和客户端都会执行 },
 beforeMount () {}, 
 mounted () {} // 客户端
}

asyncData

该方法是Nuxt最大的一个卖点,服务端渲染的能力就在这里,首次渲染时务必使用该方法。 asyncData会传进一个context参数,通过该参数可以获得一些信息,如:

export default {
 asyncData (ctx) {
  ctx.app // 根实例
  ctx.route // 路由实例
  ctx.params //路由参数
  ctx.query // 路由问号后面的参数
  ctx.error  // 错误处理方法
 }
}

渲染出错和ajax请求出错的处理

asyncData渲染出错

使用 asyncData 钩子时可能会由于服务器错误或api错误导致无法渲染,此时页面还未渲染出来,需要针对这种情况做一些处理,当遇到asyncData错误时,跳转到错误页面,nuxt提供了 context.error 方法用于错误处理,在asyncData中调用该方法即可跳转到错误页面。

export default {
  async asyncData (ctx) {
    // 尽量使用try catch的写法,将所有异常都捕捉到
    try {
      throw new Error()
    } catch {
      ctx.error({statusCode: 500, message: '服务器开小差了~' })
    }
  }
}

这样,当出现异常时会跳转到默认的错误页,错误页面可以通过 /layout/error.vue 自定义。

这里会遇到一个问题, context.error 的参数必须是类似 { statusCode: 500, message: '服务器开小差了~' }statusCode 必须是http状态码, 而我们服务端返回的错误往往有一些其他的自定义代码,如 {resultCode: 10005, resultInfo: '服务器内部错误' } ,此时需要对返回的api错误进行转换一下。

为了方便,我引入了 /plugins/ctx-inject.js 为context注册一个全局的错误处理方法: context.$errorHandler(err) 。注入方法可以参考: 注入 $root 和 context , ctx-inject.js :

// 为context注册全局的错误处理事件
export default (ctx, inject) => {
 ctx.$errorHandler = err => {
  try {
   const res = err.data
   if (res) {
    // 由于nuxt的错误页面只能识别http的状态码,因此statusCode统一传500,表示服务器异常。
    ctx.error({ statusCode: 500, message: res.resultInfo })
   } else {
    ctx.error({ statusCode: 500, message: '服务器开小差了~' })
   }
  } catch {
   ctx.error({ statusCode: 500, message: '服务器开小差了~' })
  }
 }
}

然后在 nuxt.config.js 使用该插件:

export default {
 plugins: [
  '~/plugins/ctx-inject.js'
 ]
}

注入完毕,我们就可以在 asyncData 介个样子使用了:

export default {
  async asyncData (ctx) {
    // 尽量使用try catch的写法,将所有异常都捕捉到
    try {
      throw new Error()
    } catch(err) {
      ctx.$errorHandler(err)
    }
  }
}

ajax请求出错

对于ajax的异常,此时页面已经渲染,出现错误时不必跳转到错误页,可以通过 this.$toast.error(res.message) toast出来即可。

loading方法

nuxt内置了页面顶部loading进度条的样式 推荐使用,提供页面跳转体验。 打开: this.$nuxt.$loading.start() 完成: this.$nuxt.$loading.finish()

打包部署

一般来说,部署前可以先在本地打包,本地跑一下确认无误后再上传到服务器部署。命令:

// 打包
npm run build
// 本地跑
npm start

除node_modules,.git,.env,将其他的文件都上传到服务器,然后通过 pm2 进行管理,可以在项目根目录建一个 pm2.json 方便维护:

{
 "name": "nuxt-test",
 "script": "./server/index.js",
 "instances": 2,
 "cwd": "."
}

然后配置生产环境的环境变量,一般是直接用 .env.prod 的配置: cp ./.env.prod ./.env 。 首次部署或有新的依赖包,需要在服务器上 npm install 一次,然后就可以用 pm2 启动进程啦:

// 项目根目录下运行
pm2 start ./pm2.json

需要的话,可以设置开机自动启动pm2: pm2 save && pm2 startup 。 需要注意的是,每次部署都得重启一下进程: pm2 reload nuxt-test

五、最后

Nuxt.js引入了Node,同时nuxt.config.js替代了main.js的一些作用,目录结构和vue项目都稍有不同,增加了很多的约定,对于初次接触的同学可能会觉得非常陌生,更多的内容还是得看一遍官方的文档。

demo源码: fengxianqi/front_end-demos/src/nuxt-test。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
SyntaxHighlighter代码加色使用方法
Sep 07 Javascript
js模仿jquery的写法示例代码
Jun 16 Javascript
Javascript中replace()小结
Sep 30 Javascript
jQuery回到顶部的代码
Jul 09 Javascript
Vuejs第十一篇组件之slot内容分发实例详解
Sep 09 Javascript
AngularJS过滤器filter用法分析
Dec 11 Javascript
js css自定义分页效果
Feb 24 Javascript
vue+vuex+axio从后台获取数据存入vuex实现组件之间共享数据
Apr 22 Javascript
微信小程序数据分析之自定义分析的实现
Aug 17 Javascript
Vue自定义全局Toast和Loading的实例详解
Apr 18 Javascript
基于JavaScript获取base64图片大小
Oct 18 Javascript
解决antd datepicker 获取时间默认少8个小时的问题
Oct 29 Javascript
Vue组件间通信 Vuex的用法解析
Aug 05 #Javascript
前后端常见的几种鉴权方式(小结)
Aug 04 #Javascript
vue的注意规范之v-if 与 v-for 一起使用教程
Aug 04 #Javascript
vue v-for 使用问题整理小结
Aug 04 #Javascript
在Vue项目中用fullcalendar制作日程表的示例代码
Aug 04 #Javascript
JS实现的tab切换并显示相应内容模块功能示例
Aug 03 #Javascript
原生JS 实现的input输入时表格过滤操作示例
Aug 03 #Javascript
You might like
通过JavaScript或PHP检测Android设备的代码
2011/03/09 PHP
php防止sql注入代码实例
2013/12/18 PHP
php实现微信公众平台账号自定义菜单类
2014/12/02 PHP
PHP mysqli事务操作常用方法分析
2017/07/22 PHP
php设计模式之职责链模式定义与用法经典示例
2019/09/19 PHP
基于jQuery的Spin Button自定义文本框数值自增或自减
2010/07/17 Javascript
javascript变量作用域使用中常见错误总结
2013/03/26 Javascript
函数window.open实现关闭所有的子窗口
2015/08/03 Javascript
分分钟玩转Vue.js组件
2016/10/25 Javascript
WebSocket实现简单客服聊天系统
2017/05/12 Javascript
Webpack执行命令参数详解
2017/06/17 Javascript
在iframe中使bootstrap的模态框在父页面弹出问题
2017/08/07 Javascript
微信小程序swiper实现滑动放大缩小效果
2018/11/15 Javascript
React性能优化系列之减少props改变的实现方法
2019/01/17 Javascript
Vue.js下拉菜单组件使用方法详解
2019/10/19 Javascript
基于vue hash模式微信分享#号的解决
2020/09/07 Javascript
JavaScript实现随机点名小程序
2020/10/29 Javascript
python使用any判断一个对象是否为空的方法
2014/11/19 Python
Python中操作文件之write()方法的使用教程
2015/05/25 Python
python rsa 加密解密
2017/03/20 Python
Python之自动获取公网IP的实例讲解
2017/10/01 Python
用Python登录好友QQ空间点赞的示例代码
2017/11/04 Python
Python内置模块ConfigParser实现配置读写功能的方法
2018/02/12 Python
python自动发送邮件脚本
2018/06/20 Python
使用python检查yaml配置文件是否符合要求
2020/04/09 Python
利用pandas向一个csv文件追加写入数据的实现示例
2020/04/23 Python
Python super()函数使用及多重继承
2020/05/06 Python
浅谈HTML5 Web Worker的使用
2018/01/05 HTML / CSS
canvas简单连线动画的实现代码
2020/02/04 HTML / CSS
Darphin迪梵官网: 来自巴黎,植物和精油调制的护肤品牌
2016/10/11 全球购物
英国知名奢侈品包包品牌:Milli Millu
2016/12/22 全球购物
BrandAlley英国:法国折扣奢侈品网上零售商
2017/07/03 全球购物
荷兰皇家航空公司中国官网:KLM中国
2017/12/13 全球购物
蜜蜂引路教学反思
2014/02/04 职场文书
职务说明书范文
2014/05/07 职场文书
2014年体育部工作总结
2014/11/13 职场文书