Nuxt.js vue服務端渲染



一、為什么要用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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM