Nuxt.js實(shí)現(xiàn)一個(gè)SSR的前端博客的示例代碼
為什么要用Nuxt.js
公司現(xiàn)有的項(xiàng)目只有落地頁(yè)是通過(guò)前端本身server讀取pug文件進(jìn)行服務(wù)端渲染的,當(dāng)然是為了首屏加載速度以及SEO。Nuxt.js 是一個(gè)基于Vue.js的通用應(yīng)用框架,預(yù)設(shè)了利用Vue.js開(kāi)發(fā)服務(wù)端渲染的應(yīng)用所需要的各種配置,只需要安裝官方文檔的要求進(jìn)行開(kāi)發(fā),就可以很好的解決SSR的問(wèn)題。我們以一個(gè)簡(jiǎn)單的博客為例,來(lái)實(shí)踐一下Nuxt.js。
項(xiàng)目介紹
當(dāng)前基于Nuxt.js的簡(jiǎn)化版博客,包括注冊(cè)、登錄、文章列表頁(yè)面、文章詳情頁(yè)、以及用戶(hù)列表頁(yè)等幾個(gè)頁(yè)面,用戶(hù)信息使用了Vux進(jìn)行存儲(chǔ),異步數(shù)據(jù)使用了asyncData進(jìn)行獲取,配合了nuxtServerInit、cookie來(lái)處理刷新頁(yè)面后Vux數(shù)據(jù)丟失的問(wèn)題,同時(shí)使用了error模板頁(yè)面處理常規(guī)錯(cuò)誤,使用了中間件進(jìn)行了簡(jiǎn)單的權(quán)限校驗(yàn)。該項(xiàng)目不足點(diǎn),統(tǒng)一封裝了axios的方法,但是沒(méi)有考慮到服務(wù)端請(qǐng)求接口,token的處理。
目錄結(jié)構(gòu)
- assets: 資源文件。用于組織未編譯的靜態(tài)資源如 LESS、SASS或 JavaScript。
- components: 組件。
- layouts: page: 模板頁(yè)面,默認(rèn)為 default.vue可以在這個(gè)目錄下創(chuàng)建全局頁(yè)面的統(tǒng)一布局,或是錯(cuò)誤處理頁(yè)面頁(yè),需要提供一個(gè)nuxt 標(biāo)簽,類(lèi)似于router-view
- middleware: 中間件,放置自定義的中間件,會(huì)在加載組件之前調(diào)用。可以在頁(yè)面中調(diào)用: middleware: '中間件名稱(chēng)'。
- pages: 頁(yè)面,index.vue 為根頁(yè)面,Nuxt.js 框架讀取該目錄下所有的 .vue文件并自動(dòng)生成對(duì)應(yīng)的路由配置,如需要?jiǎng)討B(tài)參數(shù)id,則可以添加_id的文件,必須是下劃線加參數(shù)名。
- plugin: 插件,用于組織那些需要在 根Vue.js應(yīng)用實(shí)例化之前需要運(yùn)行的 Javascript 插件。
- static: 靜態(tài)文件,靜態(tài)文件目錄 static用于存放應(yīng)用的靜態(tài)文件,此類(lèi)文件不會(huì)被 Nuxt.js 調(diào)用 Webpack 進(jìn)行構(gòu)建編譯處理。
- store: 用于組織vuex狀態(tài)管理。具體使用請(qǐng)移步至 官網(wǎng)。
- nuxt.config.js: nuxt.config.js文件用于組織Nuxt.js 應(yīng)用的個(gè)性化配置,配置head,loading,css,plugins等。
Nuxt.js生命周期
1. incoming Request 瀏覽器發(fā)出的請(qǐng)求)
2. nuxtServerInit 服務(wù)端接受請(qǐng)求后,要檢查當(dāng)前有沒(méi)有 nuxtServerInit配置項(xiàng),如果有就執(zhí)行這個(gè)函數(shù)
3. store action 用來(lái)操作vuex
4. middleware 可以做jWT等一些操作。
5. validate() 檢驗(yàn)參數(shù),參數(shù)檢驗(yàn)失敗,可以在layout里的error里面進(jìn)行捕捉。
6. asyncData()& fetch() asyncData用來(lái)渲染組件,fetch用來(lái)渲染vuex
7. Render
Nuxt擴(kuò)展以后的生命周期和方法以下:
beforeCreate: ƒ beforeCreate() components: {NuxtLoading: {…}} computed: {isOffline: ƒ} context: {isStatic: false, isDev: true, isHMR: true, app: {…}, payload: undefined, …} created: ƒ created() data: ƒ data() head: {title: "nuxt-meituan-ssr", meta: Array(3), link: Array(1), style: Array(0), script: Array(0)} methods: {refreshOnlineStatus: ƒ, refresh: ƒ, errorChanged: ƒ, setLayout: ƒ, loadLayout: ƒ} mounted: ƒ mounted() nuxt: {…} render: ƒ render(h, props) router: VueRouter {app: Vue, apps: Array(1), options: {…}, beforeHooks: Array(2), resolveHooks: Array(0), …} watch: {nuxt.err: "errorChanged"}
注意:
- Vue.js生命周期的鉤子只有beforeCreate和created會(huì)在服務(wù)端和客戶(hù)端渲染。
- 以上生命周期里都獲取不到window對(duì)象。
- asyncData和fetch我們可以拿到數(shù)據(jù),不要嘗試掛載數(shù)據(jù)到data上,此時(shí)獲取不到this對(duì)象。
開(kāi)發(fā)總結(jié)
如何修改默認(rèn)啟動(dòng)端口?
可以在package.json下面修改配置,如下。
"config":{ "nuxt":{ "host":"127.0.0.1", "port":"3304" } }
如何添加全局的樣式?
可以在assets里添加全局Css文件,如在assets下的Css文件夾目錄下添加了一個(gè)index.css文件,然后在nuxt-config.js里配置該css文件路徑即可。 css:['~assers/css/index.css']
通過(guò)別名訪問(wèn)圖片在template里是正確的,為何在Css設(shè)置背景圖卻報(bào)錯(cuò)?
在css配置的是,需要將'~/'后面的'/'去除掉。
<img src="~/static/logo.jpg"/> backround-image:url('~static/logo.jpg');
如何添加路由動(dòng)畫(huà)?
同樣,我們?cè)贑ss文件里添加一些動(dòng)畫(huà)代碼,一般樣式會(huì)在其后面添加-active和-leave-active,其實(shí)和Vue動(dòng)畫(huà)形式一致。其中以page開(kāi)頭的動(dòng)畫(huà),默認(rèn)會(huì)作用于全部頁(yè)面,如果想給特定的頁(yè)面加動(dòng)畫(huà),可以在對(duì)應(yīng)的頁(yè)面script里引用,如 transitions: 'bounce'即可。
.page-enter-active, .page-leave-active { transition: opacity .3s } .page-enter, .page-leave-active { opacity: 0 } .bounce-enter-active { animation: bounce-in .8s; } .bounce-leave-active { animation: bounce-out .5s; } @keyframes bounce-in { 0% { transform: scale(1) } 50% { transform: scale(1.01) } 100% { transform: scale(1) } } @keyframes bounce-out { 0% { transform: scale(1) } 50% { transform: scale(1.01) } 100% { transform: scale(1) } }
路由參數(shù)如何傳遞?
同Vue-router,有聲明式和編程式兩種方式,無(wú)非是標(biāo)簽變成了 router.push(...)
nuxt-link :to="{name:'article',params:{id:1234}}" >聲明式</nuxt-link> // 編程式 this.$router.push({ name:'article', params:{ id:1234 } })
動(dòng)態(tài)路由如何進(jìn)行參數(shù)檢驗(yàn)?
Nuxt.js提供了一個(gè)validate的生命周期鉤子,可以在此進(jìn)行參數(shù)的校驗(yàn)。以文章詳情校驗(yàn)id為例,我們需要判斷傳入的id是否是數(shù)字,可以像下面這樣處理。
validate({ params }) { return /^\d+$/.test(params.id) }
如何添加404等錯(cuò)誤頁(yè)面?
可以在layout下新建一個(gè)error.vue頁(yè)面,內(nèi)容如下,當(dāng)訪問(wèn)一個(gè)不存在的頁(yè)面的時(shí)候,或者參數(shù)檢驗(yàn)失敗的時(shí)候,或者我們?cè)趍iddleware中間件處理拋出異常的時(shí)候,都會(huì)跳轉(zhuǎn)到該頁(yè)面。
<template> <div class="container"> <h1 v-if="error.statusCode === 404">頁(yè)面不存在</h1> <h1 v-else>應(yīng)用發(fā)生錯(cuò)誤異常</h1> <nuxt-link to="/">首 頁(yè)</nuxt-link> </div> </template> <script> export default { props: ['error'], layout: 'blog' // 指定模板頁(yè)面 } </script>
middleware中的文件拋出錯(cuò)誤
export default function({ store, error, redirect }) { if (!store.state.user.userInfo.auth) { error({ message: '沒(méi)有權(quán)限哦!', statusCode: 403 }) } }
頂部進(jìn)度條如何設(shè)置?
loading 屬性配置 可以在nuxt-config.js設(shè)置loading的顏色,使用了this. loading可能無(wú)法在created里立即使用。此種配置loading有嚴(yán)重的缺陷,無(wú)法知道真正的加載進(jìn)度。也可以自定義加載組件,loading: '~components/loading.vue'。
export default { mounted () { this.$nextTick(() => { this.$nuxt.$loading.start() setTimeout(() => this.$nuxt.$loading.finish(), 500) }) } }
異步數(shù)據(jù)如何獲?。?/strong>
Nuxt.js提供了兩個(gè)函數(shù),asyncData和fetch函數(shù)。asyncData 獲取組件的數(shù)據(jù),fetch 在渲染頁(yè)面之前獲取數(shù)據(jù)填充應(yīng)用的狀態(tài)樹(shù)(store)。
asyncData可以使用promise也可以使用async函數(shù),記住,此時(shí)返回的東西需要用一個(gè)對(duì)象進(jìn)行包裹,不能掛載到data里,此時(shí)沒(méi)有this對(duì)象。
// 方式一 asyncData({ app,params,route,query,error}) { return getUserlist({}).then(res => { let user = []; user = res.list console.log(user,'user') return {user} }) .catch(err => { console.log(err) }) }, // 方式二 async asyncData({ app }) { let data = await getUserlist({}); let user = data.list; return { user } }
fetch函數(shù)同上,可以使用promise也可以使用async函數(shù),通常會(huì)commit一個(gè)mutation。
export default { fetch ({ store, params }) { return axios.get('http://my-api/stars') .then((res) => { store.commit('setStars', res.data) }) } } </script> // 或者使用 async 或 await 的模式簡(jiǎn)化代碼如下: <template> <h1>Stars: {{ $store.state.stars }}</h1> </template> <script> export default { async fetch ({ store, params }) { let { data } = await axios.get('http://my-api/stars') store.commit('setStars', data) } } </script>
如何動(dòng)態(tài)修改title的內(nèi)容?
如果是寫(xiě)死的,可以直接修改head的配置。
head() { return { // title: '',這里一旦聲明,在asyncdata里修改也不起作用,直接以這個(gè)為準(zhǔn) meta: [ { hid: 'description', // nuxt.config 替換唯一標(biāo)識(shí) hid { hid: 'description', name: 'description', content: 'Nuxt.js project' } name: 'content', content: '文章詳情' } ] } },
如果是動(dòng)態(tài)數(shù)據(jù)從數(shù)據(jù)源里獲取,然后通過(guò)asynData里的app對(duì)象,動(dòng)態(tài)修改head的title。
asyncData({ app, params }) { const id = params.id; return getArticleDetail({ id }) .then(result => { app.head.title = result.title; }) .catch(err => {}) }
如何進(jìn)行權(quán)限JWT驗(yàn)證?
登錄成功以后,我們會(huì)在cookie和Vuex中緩存token信息,當(dāng)界面刷新的時(shí)候,會(huì)走store里的nuxtServerInit 函數(shù),該函數(shù)僅在每個(gè)服務(wù)器端渲染中運(yùn),可以使用req.headers.cookie獲取瀏覽器的cookie,再次更新store里的值,接著會(huì)走到中間件,中間件進(jìn)行驗(yàn)證,如果有token信息則繼續(xù),沒(méi)有則跳轉(zhuǎn)到登錄頁(yè)。
1. 為什么要在nuxtServerInit更新store的值?
需要在middleware里使用,否則刷新后store里的值為空了。
2. 客戶(hù)端調(diào)用接口可以拿到token,服務(wù)器端如何拿到?
可以通過(guò)nuxtServerInit里的req拿到請(qǐng)求信息的cookie,然后請(qǐng)求接口。
3. 前后端分離,刷新的時(shí)候如何保證用戶(hù)名、token等信息依然存在?
可以像上面一樣,每次取cookie的值再次更新store,但這樣有一個(gè)問(wèn)題,cookie可能會(huì)被篡改,后端代碼需要做驗(yàn)證。也可以每次刷新重新通過(guò)token請(qǐng)求接口,更新用戶(hù)信息。
store代碼
import Vue from 'vue'; import Vuex from 'vuex'; import user from './modules/user'; import { COOKIE_KEY } from '~/assets/js/constant.js'; Vue.use(Vuex); const store = () => new Vuex.Store({ modules: { user }, actions: { async nuxtServerInit({ commit, dispatch }, { req, app }) { if (req.headers.cookie) { let parsedResult = {}; req.headers.cookie.split(';').forEach(cookie => { const currentCookie = cookie.split('='); parsedResult[currentCookie[0].trim()] = (currentCookie[1] || '').trim(); }); const userInfo = { name: parsedResult[COOKIE_KEY.NAME], token: parsedResult[COOKIE_KEY.TOKEN] }; commit('user/setUserInfo',userInfo); } } } }); export default store;
中間件代碼
export default function({ store, error, redirect }) { if (!store.state.user.userInfo.token || !store.state.user.userInfo.name) { // error({ // message: 'You are not connected', // statusCode: 403 // }) redirect('/'); } }
nginx部署
npm run build
選擇build以后的四個(gè)文件: .nuxt, static, nuxt.config.js, package.json上傳到服務(wù)器。
pm2 pm2 start npm --name 'package.json.name' -- run start
nginx配置
查看網(wǎng)頁(yè)源代碼可以看到:
server{ listen 3000; server_name felix12345.club; gzip on; gzip_buffers 32 4K; gzip_comp_level 6; gzip_min_length 100; gzip_types application/javascript text/css text/xml; gzip_disable "MSIE [1-6]\."; gzip_vary on; proxy_buffer_size 64k; proxy_buffers 32 32k; proxy_busy_buffers_size 128k; location / { root /data/ww/nuxt; proxy_pass http://127.0.0.1:3002; proxy_set_header X-Real-IP $remote_addr; } }
這樣,使用Nuxt.js實(shí)現(xiàn)了一個(gè)服務(wù)端渲染的簡(jiǎn)易博客。
在線訪問(wèn)地址: http://felix12345.club:3000/article/
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vues中使用JavaScript實(shí)現(xiàn)路由跳轉(zhuǎn)的步驟詳解
在Vue應(yīng)用中,利用Vue?Router進(jìn)行頁(yè)面間的導(dǎo)航是一個(gè)常見(jiàn)需求,本篇博客將通過(guò)示例代碼詳細(xì)介紹如何在Vue組件中使用JavaScript實(shí)現(xiàn)路由跳轉(zhuǎn),需要的朋友可以參考下2024-05-05vue監(jiān)聽(tīng)滾動(dòng)事件的方法
這篇文章主要介紹了vue監(jiān)聽(tīng)滾動(dòng)事件的方法,幫助大家更好的理解和使用vue,感興趣的朋友可以了解下2020-12-12在Vue3中為路由Query參數(shù)標(biāo)注類(lèi)型的方法
這篇文章主要介紹了在Vue3中如何為路由Query參數(shù)標(biāo)注類(lèi)型,我們就針對(duì)這個(gè)話題如何為路由Query參數(shù)標(biāo)注類(lèi)型為例,看看Composable和IOC容器的代碼風(fēng)格究竟有什么不同,需要的朋友可以參考下2024-08-08vue中Npm run build 根據(jù)環(huán)境傳遞參數(shù)方法來(lái)打包不同域名
這篇文章主要介紹了vue項(xiàng)目中Npm run build 根據(jù)環(huán)境傳遞參數(shù)方法來(lái)打包不同域名,使用npm run build --xxx,根據(jù)傳遞參數(shù)xxx來(lái)判定不同的環(huán)境,給出不同的域名配置,具體內(nèi)容詳情大家參考下本文2018-03-03vue生命周期beforeDestroy和destroyed調(diào)用方式
這篇文章主要介紹了vue生命周期beforeDestroy和destroyed調(diào)用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06查看vue-cli腳手架的版本號(hào)和vue真實(shí)版本號(hào)及詳細(xì)操作命令
本文給大家分享如何查看vue-cli腳手架的版本號(hào)和vue真實(shí)版本號(hào)及詳細(xì)操作過(guò)程,本文給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2022-11-11el-table表頭使用el-dropdown出現(xiàn)兩個(gè)下拉框的問(wèn)題及解決方法
本文給大家分享el-table在固定右邊列時(shí),表頭使用el-dropdown會(huì)出現(xiàn)兩個(gè)下拉框的解決方法,感興趣的朋友跟隨小編一起看看吧2024-07-07