一、为什么要用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 <项目名>


大概选上面这些选项。

值得一说的是,关于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
  • 使用:
  1. 在根目录下新建一个.env文件,并写上需要管理的环境变量,比如服务端地址APIHOST:
APIHOST=http://your_server.com/api
  1. /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 和 contextctx-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

Nuxt.js vue服务端渲染的更多相关文章

  1. 解析Nuxt.js Vue服务端渲染摸索

    本篇文章主要介绍了详解Nuxt.js Vue服务端渲染摸索,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下.如有不足之处,欢迎批评指正. Nuxt.js 十分简单易用.一个简单 ...

  2. next.js、nuxt.js等服务端渲染框架构建的项目部署到服务器,并用PM2守护程序

    前端渲染:vue.react等单页面项目应该这样子部署到服务器 貌似从前几年,前后端分离逐渐就开始流行起来,把一些渲染计算的工作抛向前端以便减轻服务端的压力,但为啥现在又开始流行在服务端渲染了呢?如v ...

  3. Vue 爬坑之路(十一)—— 基于 Nuxt.js 实现服务端渲染(SSR)

    直接使用 Vue 构建前端单页面应用,页面源码时只有简单的几行 html,这并不利于网站的 SEO,这时候就需要服务端渲染 2016 年 10 月 25 日,zeit.co 背后的团队对外发布了一个 ...

  4. [vue] vue服务端渲染nuxt.js

    初始化 使用脚手架工具 create-nuxt-app 快速创建 npx create-nuxt-app <项目名> npx create-nuxt-app 执行一些选择 在集成的服务器端 ...

  5. vue服务端渲染之nuxtjs

    前言 本篇主要针对nuxtjs中的一些重要概念整理和代码实现! 在学习vue服务端渲染之前,先搞清楚几个概念: 什么是客户端渲染(CSR) 什么是服务端渲染(SSR) CSR和SSR有什么异同 客户端 ...

  6. Egg + Vue 服务端渲染工程化实现

    在实现 egg + vue 服务端渲染工程化实现之前,我们先来看看前面两篇关于Webpack构建和Egg的文章: 在 Webpack工程化解决方案easywebpack 文章中我们提到了基于 Vue ...

  7. vue服务端渲染axios预取数据

    首先是要参考vue服务端渲染教程:https://ssr.vuejs.org/zh/data.html. 本文主要代码均参考教程得来.基本原理如下,拷贝的原文教程. 为了解决这个问题,获取的数据需要位 ...

  8. vue服务端渲染简单入门实例

    想到要学习vue-ssr的同学,自不必多说,一定是熟悉了vue,并且多多少少做过几个项目.然后学习vue服务端渲染无非解决首屏渲染的白屏问题以及SEO友好. 话不多说,笔者也是研究多日才搞明白这个服务 ...

  9. vue服务端渲染提取css

    vue服务端渲染,提取css单独打包的好处就不说了,在这里主要说的是抽取css的方法 要从 *.vue 文件中提取 CSS,可以使用 vue-loader 的 extractCSS 选项(需要 vue ...

随机推荐

  1. oslab oranges 一个操作系统的实现 实验四 认识保护模式(三):中断异常

    实验目的: 理解中断与异常机制的实现机理 对应章节:第三章3.4节,3.5节 实验内容: 1. 理解中断与异常的机制 2. 调试8259A的编程基本例程 3. 调试时钟中断例程 4. 建立IDT,实现 ...

  2. openssl的用法

    Openssl详细用法: OpenSSL 是一个开源项目,其组成主要包括一下三个组件: openssl:多用途的命令行工具 libcrypto:加密算法库 libssl:加密模块应用库,实现了ssl及 ...

  3. TypeScript Errors All In One

    TypeScript Errors All In One 1. Property 'name' has no initializer and is not definitely assigned in ...

  4. 知乎 bug

    知乎 bug shit zhihu https://zhuanlan.zhihu.com/p/111809590 无法展开评论 https://unpkg.zhimg.com/@cfe/sentry- ...

  5. Web Design Trends for 2017

    Web Design Trends for 2017 https://www.awwwards.com/web-design-trends-for-2017.html https://usersnap ...

  6. image auto downloader

    image auto downloader icons killer / js crawler http only + same-origin OK "use strict"; / ...

  7. webfullstack website

    webfullstack website refs https://www.lanqiao.cn/paths/ xgqfrms 2012-2020 www.cnblogs.com 发布文章使用:只允许 ...

  8. 以太坊手续费上涨,矿工出逃,VAST前景向好!

    根据最新数据显示,以太坊的Gas费用在最近几天大幅飙涨,尤其是在过去2小时内,增幅约20%,一度达到了17.67美元.而这也导致了,许多基于以太坊协议的相关项目无法被生态建设者使用,很多矿工也纷纷出逃 ...

  9. PAUL ADAMS ARCHITECT:澳大利亚楼市保持涨势

    澳大利亚最新房价变化显示,住宅价格指数连续第10周上涨,包括五个主要首府城市的上涨了0.29%. 12月截至24日,布里斯班以1.03%涨幅领跑,五个首府城市平均涨幅0.78%. 在过去3个月里,悉尼 ...

  10. 为什么NGK推出的DEFI项目这么火热?

    进入到2020年的下半年,DeFi的锁仓量基本上是以日破新高的态势,不断的成为一个独角兽.DeFi逐渐形成一个独角兽的同时,也在不断的给区块链生态赋能,源源不断进行金融价值输送.所以加密货币体量的不断 ...