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 相关文章推荐
excel操作之Add Data to a Spreadsheet Cell
Jun 12 Javascript
JS创建自定义表格具体实现
Feb 11 Javascript
Javascript的setTimeout()使用闭包特性时需要注意的问题
Sep 23 Javascript
AngularJs html compiler详解及示例代码
Sep 01 Javascript
微信小程序引用公共js里的方法的实例详解
Aug 17 Javascript
快速搭建React的环境步骤详解
Nov 06 Javascript
jQuery 实现左右两侧菜单添加、移除功能
Jan 02 jQuery
微信小程序实现单列下拉菜单效果
Apr 25 Javascript
vue实现信息管理系统
May 30 Javascript
vue 使用async写数字动态加载效果案例
Jul 18 Javascript
swiper实现导航滚动效果
Dec 13 Javascript
学习 Vue.js 遇到的那些坑
Feb 02 Vue.js
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
PHP中上传大体积文件时需要的设置
2006/10/09 PHP
类的另类用法--数据的封装
2006/10/09 PHP
PHP关联链接常用代码
2012/11/05 PHP
CI框架实现框架前后端分离的方法详解
2016/12/30 PHP
PHP遍历目录文件的常用方法小结
2017/02/03 PHP
PHP执行系统命令函数实例讲解
2021/03/03 PHP
键盘 keycode的值 javascript时触发事件时很有用的要素
2009/11/02 Javascript
JavaScript 页面坐标相关知识整理
2010/01/09 Javascript
用jquery写的一个万年历(自写)
2014/01/20 Javascript
js的window.showModalDialog及window.open用法实例分析
2015/01/29 Javascript
Nodejs实战心得之eventproxy模块控制并发
2015/10/27 NodeJs
牛叉的Jquery——Jquery与DOM对象的互相转换及DOM的三种操作
2015/10/29 Javascript
详解JavaScript中的Unescape()和String() 函数
2015/11/09 Javascript
javascript中Date对象应用之简易日历实现
2016/07/12 Javascript
AngularJs Scope详解及示例代码
2016/09/01 Javascript
JavaScript实现垂直滚动条效果
2017/01/18 Javascript
vue resource post请求时遇到的坑
2017/10/19 Javascript
Vue代码分割懒加载的实现方法
2017/11/23 Javascript
vue2.0 路由模式mode=&quot;history&quot;的作用
2018/10/18 Javascript
跨域请求两种方法 jsonp和cors的实现
2018/11/11 Javascript
vue中v-for通过动态绑定class实现触发效果
2018/12/06 Javascript
如何使用VuePress搭建一个类型element ui文档
2019/02/14 Javascript
vue实现文字加密功能
2019/09/27 Javascript
Python中import导入上一级目录模块及循环import问题的解决
2016/06/04 Python
python制作英语翻译小工具代码实例
2019/09/09 Python
解析python 中/ 和 % 和 //(地板除)
2020/06/28 Python
size?瑞典:英国伦敦的球鞋精品店
2018/03/01 全球购物
吉力贝官方网站:Jelly Belly
2019/03/11 全球购物
信息技术专业个人自我评价
2013/12/11 职场文书
会计专业毕业生求职信分享
2014/01/03 职场文书
会计职业生涯规划书
2014/01/13 职场文书
网络程序员自荐信
2014/01/25 职场文书
团结友爱主题班会
2015/08/13 职场文书
跟班学习心得体会(共6篇)
2016/01/23 职场文书
Vue3 Composition API的使用简介
2021/03/29 Vue.js
nginx配置限速限流基于内置模块
2022/05/02 Servers