Nuxt.js實(shí)戰(zhàn)和配置詳解
前段時(shí)間剛好公司有項(xiàng)目使用了Nuxt.js來搭建,而剛好在公司內(nèi)部做了個(gè)分享,稍微再整理一下發(fā)出來。本文比較適合初用Nuxt.js的同學(xué),主要講下搭建過程中做的一些配置。建議初次使用Nuxt.js的同學(xué)先過一遍官方文檔,再回頭看下我這篇文章。
一、為什么要用Nuxt.js
原因其實(shí)不用多說,就是利用Nuxt.js的服務(wù)端渲染能力來解決Vue項(xiàng)目的SEO問題。
二、Nuxt.js和純Vue項(xiàng)目的簡單對比
1. build后目標(biāo)產(chǎn)物不同
vue: dist
nuxt: .nuxt
2. 網(wǎng)頁渲染流程
vue: 客戶端渲染,先下載js后,通過ajax來渲染頁面;
nuxt: 服務(wù)端渲染,可以做到服務(wù)端拼接好html后直接返回,首屏可以做到無需發(fā)起ajax請求;
3. 部署流程
vue: 只需部署dist目錄到服務(wù)器,沒有服務(wù)端,需要用nginx等做Web服務(wù)器;
nuxt: 需要部署幾乎所有文件到服務(wù)器(除node_modules,.git),自帶服務(wù)端,需要pm2管理(部署時(shí)需要reload pm2),若要求用域名,則需要nginx做代理。
4. 項(xiàng)目入口
vue: /src/main.js ,在main.js可以做一些全局注冊的初始化工作; nuxt: 沒有main.js入口文件,項(xiàng)目初始化的操作需要通過 nuxt.config.js 進(jìn)行配置指定。
三、從零搭建一個(gè)Nuxt.js項(xiàng)目并配置 新建一個(gè)項(xiàng)目
直接使用腳手架進(jìn)行安裝:
npx create-nuxt-app <項(xiàng)目名>

大概選上面這些選項(xiàng)。
值得一說的是,關(guān)于 Choose custom server framework (選擇服務(wù)端框架),可以根據(jù)你的業(yè)務(wù)情況選擇一個(gè)服務(wù)端框架,常見的就是Express、Koa,默認(rèn)是None,即Nuxt默認(rèn)服務(wù)器,我這里選了 Express 。
- 選擇默認(rèn)的Nuxt服務(wù)器,不會生成
server文件夾,所有服務(wù)端渲染的操作都是Nuxt幫你完成,無需關(guān)心服務(wù)端的細(xì)節(jié),開發(fā)體驗(yàn)更接近Vue項(xiàng)目,缺點(diǎn)是無法做一些服務(wù)端定制化的操作。 - 選擇其他的服務(wù)端框架,比如
Express,會生成server文件夾,幫你搭建一個(gè)基本的Node服務(wù)端環(huán)境,可以在里面做一些node端的操作。比如我公司業(yè)務(wù)需要(解析protobuf)使用了Express,對真正的服務(wù)端api做一層轉(zhuǎn)發(fā),在node端解析protobuf后,返回json數(shù)據(jù)給客戶端。
還有 Choose Nuxt.js modules (選擇nuxt.js的模塊),可以選 axios 和 PWA ,如果選了axios,則會幫你在nuxt實(shí)例下注冊 $axios ,讓你可以在.vue文件中直接 this.$axios 發(fā)起請求。
開啟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)/
})
}
}
}
這樣開發(fā)時(shí)保存文件就可以檢查語法了。nuxt默認(rèn)使用的規(guī)則是 @nuxtjs (底層來自eslint-config-standard ),規(guī)則配置在 /.eslintrc.js :
module.exports = {
root: true,
env: {
browser: true,
node: true
},
parserOptions: {
parser: 'babel-eslint'
},
extends: [
'@nuxtjs', // 該規(guī)則對應(yīng)這個(gè)依賴: @nuxtjs/eslint-config
'plugin:nuxt/recommended'
],
// add your custom rules here
rules: {
'nuxt/no-cjs-in-config': 'off'
}
}
如果不習(xí)慣用 standard 規(guī)則的團(tuán)隊(duì)可以將 @nuxtjs 改成其他的。
使用dotenv和@nuxtjs/dotenv統(tǒng)一管理環(huán)境變量
在node端,我們喜歡使用 dotenv 來管理項(xiàng)目中的環(huán)境變量,把所有環(huán)境變量都放在根目錄下的 .env 中。
安裝:
npm i dotenv
使用: 在根目錄下新建一個(gè) .env 文件,并寫上需要管理的環(huán)境變量,比如服務(wù)端地址 APIHOST :
APIHOST=http://your_server.com/api
在 /server/index.js 中使用(該文件是選Express服務(wù)端框架自動生成的):
require('dotenv').config()
// 通過process.env即可使用
console.log(process.env.APIHOST) // http://your_server.com/api
此時(shí)我們只是讓服務(wù)端可以使用 .env 的文件而已,Nuxt客戶端并不能使用 .env ,按Nuxt.js文檔所說,可以將客戶端的環(huán)境變量放置在 nuxt.config.js 中:
module.exports = {
env: {
baseUrl: process.env.BASE_URL || 'http://localhost:3000'
}
}
但如果node端和客戶端需要使用同一個(gè)環(huán)境變量時(shí)(后面講到API鑒權(quán)時(shí)會使用同一個(gè)SECRET變量),就需要同時(shí)在 nuxt.config.js 和 .env 維護(hù)這個(gè)字段,比較麻煩,我們更希望環(huán)境變量只需要在一個(gè)地方維護(hù),所以為了解決這個(gè)問題,我找到了 @nuxtjs/dotenv 這個(gè)依賴,它使得nuxt的客戶端也可以直接使用 .env ,達(dá)到了我們的預(yù)期。
安裝:
npm i @nuxtjs/dotenv
客戶端也是通過 process.env.XXX 來使用,不再舉例啦。
這樣,我們通過 dotenv 和 @nuxtjs/dotenv 這兩個(gè)包,就可以統(tǒng)一管理開發(fā)環(huán)境中的變量啦。
另外, @nuxtjs/dotenv 允許打包時(shí)指定其他的env文件。比如,開發(fā)時(shí)我們使用的是 .env ,但我們打包的線上版本想用其他的環(huán)境變量,此時(shí)可以指定build時(shí)用另一份文件如 /.env.prod ,只需在 nuxt.config.js 指定:
module.exports = {
modules: [
['@nuxtjs/dotenv', { filename: '.env.prod' }] // 指定打包時(shí)使用的dotenv
],
}
@nuxtjs/toast模塊
toast可以說是很常用的功能,一般的UI框架都會有這個(gè)功能。但如果你的站點(diǎn)沒有使用UI框架,而alert又太丑,不妨引入該模塊:
npm install @nuxtjs/toast
然后在 nuxt.config.js 中引入
module.exports = {
modules: [
'@nuxtjs/toast',
['@nuxtjs/dotenv', { filename: '.env.prod' }] // 指定打包時(shí)使用的dotenv
],
toast: {// toast模塊的配置
position: 'top-center',
duration: 2000
}
}
這樣,nuxt就會在全局注冊 $toast 方法供你使用,非常方便:
this.$toast.error('服務(wù)器開小差啦~~')
this.$toast.error('請求成功~~')
API鑒權(quán)
對于某些敏感的服務(wù),我們可能需要對API進(jìn)行鑒權(quán),防止被人輕易盜用我們node端的API,因此我們需要做一個(gè)API的鑒權(quán)機(jī)制。常見的方案有jwt,可以參考一下阮老師的介紹: 《JSON Web Token 入門教程》 。如果場景比較簡單,可以自行設(shè)計(jì)一下,這里提供一個(gè)思路:
- 客戶端和node端在環(huán)境變量中聲明一個(gè)秘鑰:SECRET=xxxx,注意這個(gè)是保密的;
- 客戶端發(fā)起請求時(shí),將當(dāng)前時(shí)間戳(timestamp)和
SECRET通過某種算法,生成一個(gè)signature,請求時(shí)帶上timestamp和signature; - node接收到請求,獲得
timestamp和signature,將timestamp和秘鑰用同樣的算法再生成一次簽名_signature - 對比客戶端請求的
signature和node用同樣的算法生成的_signature,如果一致就表示通過,否則鑒權(quán)失敗。
具體的步驟:
客戶端對axios進(jìn)行一層封裝:
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的每一個(gè)請求都做一個(gè)處理,攜帶上簽名和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端寫一個(gè)鑒權(quán)的中間件, /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))
}
// 使用和客戶端相同的一個(gè)秘鑰
const SECRET = process.env.SECRET
function verifyMiddleware (req, res, next) {
const { sign, timestamp } = req.query
// 加密算法與請求時(shí)的一致
const _sign = crypto(`timestamp=${timestamp}&secret=${SECRET}`)
if (_sign === sign) {
next()
} else {
res.status(401).send({
message: 'invalid token'
})
}
}
module.exports = { verifyMiddleware }
最后,在需要鑒權(quán)的路由中引用這個(gè)中間件, /server/index.js :
const { Router } = require('express')
const { verifyMiddleware } = require('../middleware/verify.js')
const router = Router()
// 在需要鑒權(quán)的路由加上
router.get('/test', verifyMiddleware, function (req, res, next) {
res.json({name: 'test'})
})
靜態(tài)文件的處理
根目錄下有個(gè) /static 文件夾,我們希望這里面的文件可以直接通過url訪問,需要在 /server/index.js 中加入一句:
const express = require('express')
const app = express()
app.use('/static', express.static('static'))
四、Nuxt開發(fā)相關(guān) 生命周期
Nuxt擴(kuò)展了Vue的生命周期,大概如下:
export default {
middleware () {}, //服務(wù)端
validate () {}, // 服務(wù)端
asyncData () {}, //服務(wù)端
fetch () {}, // store數(shù)據(jù)加載
beforeCreate () { // 服務(wù)端和客戶端都會執(zhí)行},
created () { // 服務(wù)端和客戶端都會執(zhí)行 },
beforeMount () {},
mounted () {} // 客戶端
}
asyncData
該方法是Nuxt最大的一個(gè)賣點(diǎn),服務(wù)端渲染的能力就在這里,首次渲染時(shí)務(wù)必使用該方法。 asyncData會傳進(jìn)一個(gè)context參數(shù),通過該參數(shù)可以獲得一些信息,如:
export default {
asyncData (ctx) {
ctx.app // 根實(shí)例
ctx.route // 路由實(shí)例
ctx.params //路由參數(shù)
ctx.query // 路由問號后面的參數(shù)
ctx.error // 錯(cuò)誤處理方法
}
}
渲染出錯(cuò)和ajax請求出錯(cuò)的處理
asyncData渲染出錯(cuò)
使用 asyncData 鉤子時(shí)可能會由于服務(wù)器錯(cuò)誤或api錯(cuò)誤導(dǎo)致無法渲染,此時(shí)頁面還未渲染出來,需要針對這種情況做一些處理,當(dāng)遇到asyncData錯(cuò)誤時(shí),跳轉(zhuǎn)到錯(cuò)誤頁面,nuxt提供了 context.error 方法用于錯(cuò)誤處理,在asyncData中調(diào)用該方法即可跳轉(zhuǎn)到錯(cuò)誤頁面。
export default {
async asyncData (ctx) {
// 盡量使用try catch的寫法,將所有異常都捕捉到
try {
throw new Error()
} catch {
ctx.error({statusCode: 500, message: '服務(wù)器開小差了~' })
}
}
}
這樣,當(dāng)出現(xiàn)異常時(shí)會跳轉(zhuǎn)到默認(rèn)的錯(cuò)誤頁,錯(cuò)誤頁面可以通過 /layout/error.vue 自定義。
這里會遇到一個(gè)問題, context.error 的參數(shù)必須是類似 { statusCode: 500, message: '服務(wù)器開小差了~' } , statusCode 必須是http狀態(tài)碼, 而我們服務(wù)端返回的錯(cuò)誤往往有一些其他的自定義代碼,如 {resultCode: 10005, resultInfo: '服務(wù)器內(nèi)部錯(cuò)誤' } ,此時(shí)需要對返回的api錯(cuò)誤進(jìn)行轉(zhuǎn)換一下。
為了方便,我引入了 /plugins/ctx-inject.js 為context注冊一個(gè)全局的錯(cuò)誤處理方法: context.$errorHandler(err) 。注入方法可以參考: 注入 $root 和 context , ctx-inject.js :
// 為context注冊全局的錯(cuò)誤處理事件
export default (ctx, inject) => {
ctx.$errorHandler = err => {
try {
const res = err.data
if (res) {
// 由于nuxt的錯(cuò)誤頁面只能識別http的狀態(tài)碼,因此statusCode統(tǒng)一傳500,表示服務(wù)器異常。
ctx.error({ statusCode: 500, message: res.resultInfo })
} else {
ctx.error({ statusCode: 500, message: '服務(wù)器開小差了~' })
}
} catch {
ctx.error({ statusCode: 500, message: '服務(wù)器開小差了~' })
}
}
}
然后在 nuxt.config.js 使用該插件:
export default {
plugins: [
'~/plugins/ctx-inject.js'
]
}
注入完畢,我們就可以在 asyncData 介個(gè)樣子使用了:
export default {
async asyncData (ctx) {
// 盡量使用try catch的寫法,將所有異常都捕捉到
try {
throw new Error()
} catch(err) {
ctx.$errorHandler(err)
}
}
}
ajax請求出錯(cuò)
對于ajax的異常,此時(shí)頁面已經(jīng)渲染,出現(xiàn)錯(cuò)誤時(shí)不必跳轉(zhuǎn)到錯(cuò)誤頁,可以通過 this.$toast.error(res.message) toast出來即可。
loading方法
nuxt內(nèi)置了頁面頂部loading進(jìn)度條的樣式 推薦使用,提供頁面跳轉(zhuǎn)體驗(yàn)。 打開: this.$nuxt.$loading.start() 完成: this.$nuxt.$loading.finish()
打包部署
一般來說,部署前可以先在本地打包,本地跑一下確認(rèn)無誤后再上傳到服務(wù)器部署。命令:
// 打包 npm run build // 本地跑 npm start
除node_modules,.git,.env,將其他的文件都上傳到服務(wù)器,然后通過 pm2 進(jìn)行管理,可以在項(xiàng)目根目錄建一個(gè) pm2.json 方便維護(hù):
{
"name": "nuxt-test",
"script": "./server/index.js",
"instances": 2,
"cwd": "."
}
然后配置生產(chǎn)環(huán)境的環(huán)境變量,一般是直接用 .env.prod 的配置: cp ./.env.prod ./.env 。 首次部署或有新的依賴包,需要在服務(wù)器上 npm install 一次,然后就可以用 pm2 啟動進(jìn)程啦:
// 項(xiàng)目根目錄下運(yùn)行 pm2 start ./pm2.json
需要的話,可以設(shè)置開機(jī)自動啟動pm2: pm2 save && pm2 startup 。 需要注意的是,每次部署都得重啟一下進(jìn)程: pm2 reload nuxt-test 。
五、最后
Nuxt.js引入了Node,同時(shí)nuxt.config.js替代了main.js的一些作用,目錄結(jié)構(gòu)和vue項(xiàng)目都稍有不同,增加了很多的約定,對于初次接觸的同學(xué)可能會覺得非常陌生,更多的內(nèi)容還是得看一遍官方的文檔。
demo源碼: fengxianqi/front_end-demos/src/nuxt-test。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Nuxt.js踩坑總結(jié)分享
- Nuxt.js實(shí)戰(zhàn)詳解
- nuxt.js 緩存實(shí)踐
- Nuxt.js實(shí)現(xiàn)校驗(yàn)訪問瀏覽器類型的中間件
- Nuxt.js開啟SSR渲染的教程詳解
- Nuxt.js之自動路由原理的實(shí)現(xiàn)方法
- nuxt.js中間件實(shí)現(xiàn)攔截權(quán)限判斷的方法
- 詳解Nuxt.js Vue服務(wù)端渲染摸索
- Vue在 Nuxt.js 中重定向 404 頁面的方法
- Nuxt.js SSR與權(quán)限驗(yàn)證的實(shí)現(xiàn)
- Nuxt.js 數(shù)據(jù)雙向綁定的實(shí)現(xiàn)
相關(guān)文章
vue鼠標(biāo)懸停事件監(jiān)聽實(shí)現(xiàn)方法
頁面在鼠標(biāo)懸停(不動)n秒之后,頁面進(jìn)行相應(yīng)的事件,下面這篇文章主要給大家介紹了關(guān)于vue鼠標(biāo)懸停事件監(jiān)聽的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
vue el-select綁定對象時(shí),回顯內(nèi)容不正確,始終是最后一項(xiàng)的解決
這篇文章主要介紹了vue el-select綁定對象時(shí),回顯內(nèi)容不正確,始終是最后一項(xiàng)的問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
ElementUI動態(tài)渲染el-table的實(shí)現(xiàn)過程
在前端開發(fā)中,表格是不可或缺的一部分,無論是數(shù)據(jù)展示、數(shù)據(jù)錄入,還是數(shù)據(jù)分析,表格都扮演著重要的角色,而在Vue.js生態(tài)系統(tǒng)中,ElementUI提供了一個(gè)強(qiáng)大且靈活的表格組件——el-table,本文將帶你深入了解如何使用ElementUI動態(tài)渲染el-table,并詳細(xì)探討其原理及實(shí)現(xiàn)過程2024-08-08

