前段時間恰好公司有項目使用了Nuxt.js來搭建,而恰好在公司內部作了個分享,稍微再整理一下發出來。本文比較適合初用Nuxt.js的同窗,主要講下搭建過程當中作的一些配置。建議初次使用Nuxt.js的同窗先過一遍官方文檔,再回頭看下我這篇文章。html
緣由其實不用多說,就是利用Nuxt.js的服務端渲染能力來解決Vue項目的SEO問題。vue
vue: distnode
nuxt: .nuxtios
vue: 客戶端渲染,先下載js後,經過ajax來渲染頁面;nginx
nuxt: 服務端渲染,能夠作到服務端拼接好html後直接返回,首屏能夠作到無需發起ajax請求;git
vue: 只需部署dist目錄到服務器,沒有服務端,須要用nginx等作Web服務器;github
nuxt: 須要部署幾乎全部文件到服務器(除node_modules,.git),自帶服務端,須要pm2管理(部署時須要reload pm2),若要求用域名,則須要nginx作代理。web
vue: /src/main.js
,在main.js能夠作一些全局註冊的初始化工做; nuxt: 沒有main.js入口文件,項目初始化的操做須要經過nuxt.config.js
進行配置指定。ajax
直接使用腳手架進行安裝:算法
npx create-nuxt-app <項目名>
複製代碼
值得一說的是,關於Choose custom server framework
(選擇服務端框架),能夠根據你的業務狀況選擇一個服務端框架,常見的就是Express、Koa,默認是None,即Nuxt默認服務器,我這裏選了Express
。
server
文件夾,全部服務端渲染的操做都是Nuxt幫你完成,無需關心服務端的細節,開發體驗更接近Vue項目,缺點是沒法作一些服務端定製化的操做。Express
,會生成server
文件夾,幫你搭建一個基本的Node服務端環境,能夠在裏面作一些node端的操做。好比我公司業務須要(解析protobuf)使用了Express
,對真正的服務端api作一層轉發,在node端解析protobuf後,返回json數據給客戶端。還有Choose Nuxt.js modules
(選擇nuxt.js的模塊),能夠選axios
和PWA
,若是選了axios,則會幫你在nuxt實例下注冊$axios
,讓你能夠在.vue文件中直接this.$axios
發起請求。
在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
改爲其餘的。
在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 ], } 複製代碼
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進行鑑權,防止被人輕易盜用咱們node端的API,所以咱們須要作一個API的鑑權機制。常見的方案有jwt,能夠參考一下阮老師的介紹:《JSON Web Token 入門教程》。若是場景比較簡單,能夠自行設計一下,這裏提供一個思路:
SECRET
經過某種算法,生成一個signature
,請求時帶上timestamp
和signature
;timestamp
和signature
,將timestamp
和祕鑰用一樣的算法再生成一次簽名_signature
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擴展了Vue的生命週期,大概以下:
export default { middleware () {}, //服務端 validate () {}, // 服務端 asyncData () {}, //服務端 fetch () {}, // store數據加載 beforeCreate () { // 服務端和客戶端都會執行}, created () { // 服務端和客戶端都會執行 }, beforeMount () {}, mounted () {} // 客戶端 } 複製代碼
該方法是Nuxt最大的一個賣點,服務端渲染的能力就在這裏,首次渲染時務必使用該方法。 asyncData會傳進一個context參數,經過該參數能夠得到一些信息,如:
export default { asyncData (ctx) { ctx.app // 根實例 ctx.route // 路由實例 ctx.params //路由參數 ctx.query // 路由問號後面的參數 ctx.error // 錯誤處理方法 } } 複製代碼
使用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的異常,此時頁面已經渲染,出現錯誤時沒必要跳轉到錯誤頁,能夠經過this.$toast.error(res.message)
toast出來便可。
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項目都稍有不一樣,增長了不少的約定,對於初次接觸的同窗可能會以爲很是陌生,更多的內容仍是得看一遍官方的文檔。