服務端預渲染之Nuxt (使用篇)
現在大多數開發都是基於Vue
或者React
開發的,能夠達到快速開發的效果,也有一些不足的地方,Nuxt
能夠在服務端做出渲染,然後讓搜尋引擎在爬取資料的時候能夠讀到當前頁面。
首先要說明一點,我們可以認為我們所編寫的Vue
專案是一個服務端的專案,雖然編寫的還是Vue
專案,但是Nuxt
是基於伺服器環境的。
就簡單的說一下Nuxt
使用。基礎只是還是以官方文件為主,如果部落格中哪裡有問題,歡迎留言指正。
說了這麼多,進入正題。
路由
與傳統的Vue
專案不同的是,我們在使用Vue
的時候需要配置Vue-Router
資訊,在Nuxt
有很關鍵的一點就是約定優於配置
。page
目錄下的所有*.vue
檔案會自動生成路由配置。
在專案初始化之後,在pages
下面預設有一個index.vue
檔案,所以當我們使用npm run dev
啟動專案,並且使用http://localhost:3000/
訪問的時候能夠正常訪問路由。
為了證實上面這一點,在pages
下面建立一個資訊about.vue
檔案,並且http://localhost:3000/about
去訪問剛剛寫的頁面。我們可以按照正常的Vue
頁面去開發就好了。
page目錄
├─page │├─index.vue └───└─about.vue
about.vue
<template> <div> <h2>This About</h2> </div> </template>
建立完成之後使用http://localhost:3000/about
訪問該頁面,頁面能夠正常的渲染出來了。就會看到This About
顯示在頁面中。
做到這一步之後就應該實現路由之間的跳轉了。Vue
開發過程中,都是使用router-link
標籤完成路由之間的跳轉,在Nuxt
也同樣可以使用router-link
,但是Nuxt
仍然推薦使用nuxt-link
,nuxt-link
與router-link
的功能是等效的。
可能會有一些疑問,既然是等效的,為什麼要使用nuxt-link
呢?官方文件中是這樣說的:將來我們會為nuxt-link
元件增加更多的功能特性,例如資源預載入,用於提升nuxt.js
應用的響應速度。顯然嘛,官方不會無緣無故的就做出這麼一個東西來,肯定實在其中做了很多的優化工作的。
稍微的改動一下剛才的about.vue
在裡面新增兩個標籤,一個使用nuxt-link
,一個使用router-link
看下能否正常完成跳轉。
about.vue - 更改後
<template> <div> <h2>This About</h2> <nuxt-link to="/">首頁</nuxt-link> <router-link to="/">首頁</router-link> </div> </template>
既然從路由開始那麼就不得不說到子路由,全域性路由守衛這些都些在路由中經常用到的應該怎麼處理?該怎麼解決這些問題。
前面既然說到了Nuxt
會把pages
資料夾下面的所有*.vue
檔案編譯成路由,那麼子路由需要使用資料夾巢狀才行。
接下來就嘗試一下。首先要更改一下pgeas
目錄結構。
page目錄
├─page │├─about ││├─detail.vue ││└─index.vue └───└─index.vue
注意上面的about
目錄,是index.vue
而並非about.vue
,這裡的index.vue
指的是about
路由下的首頁,也就是最開始放在與index.vue
同級的那個about.vue
是一樣的效果。
about/index.vue
<template> <div> <h2>This About</h2> <nuxt-link to="/">首頁</nuxt-link> <router-link to="/">首頁</router-link> </div> </template>
about/detail.vue
<template> <div> <h2>This Detail</h2> </div> </template>
現在如果我們想要訪問剛才的那兩個路由地址分別就是http://localhost:3000/about
和http://localhost:3000/about/detail
就能看到剛才編寫的page
頁面了。
如果想要看路由生成到底是什麼樣子的?可以在根目錄下有一個.nuxt
資料夾,在裡面可以看到一個router.js
,這個資料夾下面就是Nuex
生成好的路由資訊。
開啟檔案後翻到最後會有一段這樣的程式碼,是不是很眼熟?這是不就是在編寫Vue
專案的時候配置的哪些路由檔案麼?
router.js
export function createRouter() { return new Router({ mode: 'history', base: decodeURI('/'), linkActiveClass: 'nuxt-link-active', linkExactActiveClass: 'nuxt-link-exact-active', scrollBehavior, routes: [{ path: "/about", component: _9ceb4424, name: "about" }, { path: "/about/detail", component: _18146f65, name: "about-detail" }, { path: "/", component: _d3bf5a4e, name: "index" }], fallback: false }) }
有了這個檔案的話我們就可以清楚的知道,路由的結構了。不僅僅這樣,還可以使用name
去實現路由的跳轉了。
需要注意的是,如果你的路由是有資料夾巢狀的話,Nuxt
是用使用-
來拼接路由的name
名稱的(如:about-detail
),但是資料夾內部的index.vue
會直接已資料夾的名字作為name
。一旦知道了路由的name
,這樣我們就可以使用命令的方式跳轉路由了。
再次更改一下about/index.vue
。
about/index.vue
<template> <div> <h2>This About</h2> <nuxt-link :to="{name:'about-detail'}">詳情</nuxt-link> <router-link :to="{name:'index'}">首頁</router-link> <button @click="onClick">跳轉到詳情</button> </div> </template> <script> export default { methods: { onClick() { this.$router.push({name:"about-detail"}) } } } </script>
使用路由訪問http://localhost:3000/about
地址,分別點選詳情、首頁與button
,都是能夠正常跳轉的,與之前的Vue
開發是完全沒有任何區別的。在vue-router
中有一個很重要的一個點就是動態路由
的概念,如果想要實現動態路由應該怎麼處理呢?
如果想要在Nuxt
中使用動態路由的話,需要在對應的路由下面新增一個_引數名.vue
的檔案,在about
檔案下面新增一個_id.vue
page目錄
├─page │├─about ││├─detail.vue ││├─_id.vue ││└─index.vue └───└─index.vue
新建完成之後在去router.js
中看一下更改後的路由結構
export function createRouter() { return new Router({ mode: 'history', base: decodeURI('/'), linkActiveClass: 'nuxt-link-active', linkExactActiveClass: 'nuxt-link-exact-active', scrollBehavior, routes: [{ path: "/about", component: _9ceb4424, name: "about" }, { path: "/about/detail", component: _18146f65, name: "about-detail" }, { path: "/about/:id", component: _6b59f854, name: "about-id" }, { path: "/", component: _d3bf5a4e, name: "index" }], fallback: false }) }
可以明顯的看到在/about/:id
這個路由,明顯的變化不止這些變動的還有name: "about-id"
不再是之前的name:about
了。如果想要使用這個id
的話必須在_id.vue
中才能獲取到。
**_id.vue**
<template> <div> {{$route.params.name}} {{$route.params.id}} </div> </template>
在_id.vue
中編寫以上程式碼並使用http://localhost:3000/about/ABC
,可以看到在頁面中已經展示了當前的id
值。
在實際開發過程當中可能params
可能會有多個引數,又應該怎麼處理呢?
調整目錄結構
//id為可選引數 ├─page │├─about ││├─_name |||└─_id |||└─index.vue ││└─index.vue └───└─index.vue
**about - _name - _id.vue**
<template> <div> {{$route.params.name}} {{$route.params.id}} </div> </template>
弄完之後看下router.js
的變化
export function createRouter() { return new Router({ mode: 'history', base: decodeURI('/'), linkActiveClass: 'nuxt-link-active', linkExactActiveClass: 'nuxt-link-exact-active', scrollBehavior, routes: [{ path: "/about", component: _9ceb4424, name: "about" }, { path: "/about/detail", component: _18146f65, name: "about-detail" }, { path: "/about/:name", component: _2ec9f53c, name: "about-name" }, { path: "/about/:name/:id", component: _318c16a4, name: "about-name-id" }, { path: "/", component: _d3bf5a4e, name: "index" }], fallback: false }) }
這裡展示的是第二種情況,id
為必選引數的情況,路由被編譯的結果。
雖然路由已經添加了引數,但是id
屬性不是必填屬性,這樣的話不能滿足專案需求又要如何處理呢?很簡單的,在_id.vue
檔案同目錄下新增一個index.vue
檔案就可以了。
//id為必選引數 ├─page │├─about ││├─_name |||├─_id.vue |||└─index.vue ││└─index.vue └───└─index.vue
需要注意的是,一定要在_id.vue
檔案中使用傳入的引數,直接獲取在index.vue
中是拿不到任何資訊的。但是如果訪問http://localhost:3000/about/ABC
這樣的路由的話,實在index.vue
中是可以獲取到name
引數的。
在剛才的router.js
檔案中生成的所有的路由都是平級的,如何實現路由的巢狀,如果想要實現巢狀路由的話,必須有和當前路由同名的資料夾存在,才能完成路由的巢狀。
page目錄
├─page │├─about ||├─_id.vue ||└─detaile.vue │├─about.vue └───└─index.vue
router.js
export function createRouter() { return new Router({ mode: 'history', base: decodeURI('/'), linkActiveClass: 'nuxt-link-active', linkExactActiveClass: 'nuxt-link-exact-active', scrollBehavior, routes: [{ path: "/about", component: _76687814, children: [{ path: "", component: _9ceb4424, name: "about" }, { path: ":id", component: _6b59f854, name: "about-id" }] }, { path: "/", component: _d3bf5a4e, name: "index" }], fallback: false }) }
更改完目錄結構,那我們巢狀的路由應該如何展示?在vue.js
中開發的時候使用router-view
這個標籤完成的。為了效能的優化Nuxt
也提供了一個對應的標籤nuxt-child
。
如果想實現巢狀路由傳參需要稍微的改動一下目錄結構,按照上面的方法實現就好了,下面是一個路由結構的例子。
page目錄
├─page │├─about ││├─detail |||├─_id.vue |||└─index.vue ││└─index.vue └───└─index.vue
router.js
export function createRouter() { return new Router({ mode: 'history', base: decodeURI('/'), linkActiveClass: 'nuxt-link-active', linkExactActiveClass: 'nuxt-link-exact-active', scrollBehavior, routes: [{ path: "/about", component: _76687814, name: "about", children: [{ path: "detail", component: _0a09b97d, name: "about-detail" }, { path: "detail/:id?", component: _fa7c11b6, name: "about-detail-id" }] }, { path: "/", component: _d3bf5a4e, name: "index" }], fallback: false }) }
在_id.vue
中則可以使用id這個引數了。訪問路由http://localhost:3000/about/detail/123
,依然可以拿到傳入的id
為123
的這個引數。
說了這麼多了,還有很多問題沒得說完,關於路由的全域性守衛又應該如何去使用?在Nuxt
根目錄下有個plugins
資料夾。首先要做的是在裡面建立一個名為router.js
檔案。
plugins-router.js
export default ({app}) => { app.router.beforeEach((to,form,next) => { console.log(to) next(); }); }
匯出了一個函式,在這個函式中可以通過結構拿到vue
的例項物件名叫app
。需要注意的是,這個beforeEach
函式的執行,有可能會在服務端也會有可能在客戶端輸出。客戶端首次訪問的頁面會在服務端做輸出,一旦渲染完成之後,則不會再在服務端輸出,則會一直在客戶端進行輸出了。
說到這裡做個小插曲,那麼又該怎麼區分當前是在客戶端環境還是服務端環境呢?可以使用process.server
獲取到當前的執行環境,其得到的是Boolean
值,true
服務端,fasle
客戶端。
做了這些之後去訪問路由,彷彿沒有任何輸出,無論實在客戶端還是在服務端,都沒有任何列印輸出,中間缺少了步驟,需要在根目錄下找到nuxt.config.js
對外掛進行配置。
nuxt.config.js
const pkg = require('./package') module.exports = { plugins: [ '@/plugins/element-ui', '@/plugins/router' ] }
由於更改了Nuxt
配置需要重啟一下服務,才能正常執行剛剛寫入的外掛。然後訪問剛剛寫入的路由,會看在服務端初次渲染的時候,會輸出我們想要的那些東西,進行路由跳轉的話,會在客戶端輸出,這也就證明了Nuxt
只會做首屏的伺服器渲染。
路由說了這麼接下來需要說一下Nuxt
是如何為指定的路由配置資料做渲染的。其實Nuxt
在做渲染的時候包裹了很多層。首先有一個Document
作為其模板,然後再去尋找其佈局的頁面,找到對應的頁面之後,再根據引用去找到相關的元件進行渲染,資料請求與資料掛載,一系列完成之後,把剩餘的路由資訊返還給客戶端,渲染完成,這個就是Nuxt
簡單的渲染流程。
在上面提到了一個佈局頁面
,這個東西應該去哪裡找?又應該怎麼做呢?它對於專案而言對於開發又有什麼好處?在Nuxt
根目錄下有一個layouts
資料夾,下面有一個default.vue
這個檔案就是上面提到的渲染頁面,也就同等於vue
開發中的App.vue
,在這裡可以做很多事情。例如新增一個全域性的導航。
在layouts
資料夾新增一個about.vue
檔案寫入如下內容,接下來需要在pages
下面的about.vue
中通知,對應pages
使用哪個佈局頁面,不寫則使用預設,然後訪問http://localhost:3000/about
相關的頁面,只要是和about
相關的頁面,都會展示這個內容。
layouts - about.vue
<template> <div> <h2>Aaron 個人部落格主頁</h2> <nuxt></nuxt> </div> </template>
pages - about.vue
<template> <div> <h2>About</h2> <nuxt-child></nuxt-child> </div> </template> <script> export default { layout:"about" } </script>
訪問一下所有與about
頁面有關的頁面,都會看到Aaron個人部落格主頁
這個字樣,若訪問根路由則無法看到的。
如果做過mvc
開發的話,如果頁面發生錯誤會跳轉到一個錯誤頁面的。Nuxt
也是有預設的錯誤頁面的,但是全是英文而且樣式也不太好看,不能自定義樣式。如何自定義錯誤頁面呢?
在layouts
資料夾中新建一個error.vue
檔案。
layouts - error.vue
<template> <div> <h1>這裡是錯誤頁面</h1> <h2 v-if="error.statusCode == 404">404 - 頁面不存在</h2> <h2 v-else>500 - 伺服器錯誤</h2> <ul> <li><nuxt-link to="/">HOME</nuxt-link></li> </ul> </div> </template> <script> export default { props:["error"] } </script>
在error.vue
中可以通過props
拿到一個error
物件,獲取到error
錯誤資訊之後能做任何想要做的事情。需要注意的一點是,自定意的錯誤頁面,只能在客戶端訪問失效的時候才會響應到該頁面,若在服務端的話,是無法直接渲染這個頁面的。
更改頁面配置Nuxt
中有些全域性的配置,配置資訊在nuxt.config.js
更改其全域性配置,pages
資料夾中的*.vue
檔案也是可以配置的,頁面私有的配置會覆蓋掉全域性的配置。
舉例:
export default { layout:"about", head: { title:"About" } }
在這些全域性配置中最重要的一個就是asyncData
這個屬性。asyncData
到底是用來做什麼的呢?這個資料可以在設定元件的資料之前能一步獲取或者處理資料。也就是說在元件渲染之前先獲取到資料,然後等待掛載渲染。
舉個例子:
<template> <div> <h2>姓名:{{userInfo.name}}</h2> <h2>年齡:{{userInfo.age}}</h2> <nuxt-child></nuxt-child> </div> </template> <script> let getUserInfo = () => { return new Promise(resolve => { setTimeout(() => { let data = {"name":"Aaron","age":18}; resolve(data); }) }) } export default { layout:"about", head: { title:"About" }, async asyncData(){ const userInfo = await getUserInfo(); return {userInfo} } } </script>
一定要return
出去獲取到的物件,這樣就可以在元件中使用,這裡返回的資料會和元件中的data
合併。這個函式不光在服務端會執行,在客戶端同樣也會執行。
注意事項:
this
剛剛提到了一點就是上下問物件,在上線文物件中可以獲取到很多東西,如路由引數,錯誤資訊等等等,這裡就不作太多贅述了,有了這些可以做一些頁面的重定向或者其他工作,比如引數校驗,安全驗證等工作。
路由扯了一大堆,接下來說一下如何在Nuxt
中融入axios
的使用。
安裝axios
npm install @nuxtjs/axios --save-dev
安裝完成後更改配置資訊:
nuxt.config.js
module.exports = { modules: [ // Doc: https://axios.nuxtjs.org/usage '@nuxtjs/axios', ], axios: { proxy:true//代理 }, proxy: { "/api/":"http://localhost:3001/"//key(路由字首):value(代理地址) } }
主要說名一下proxy
這裡,/api/
在請求的時候遇到此字首則會只指向的代理地址去請求資料。
既然說到了axios
,就不得不提到的一個東西就是攔截器,很是有用在專案開發過程中必不可少的。
舉個例子:
module.expores{ plugins: [ '@/pluginx/axios' ] }
plugins/axios.js
export default ({$axios,request}) => { $axios. onRequest((config) => { config.headers.token = "Aaron" }) }
總結
說了這麼多也許會有些紕漏,或者遺漏的知識點,若有什麼錯誤的地方可以在下方留言,儘快做出改正。謝謝大家花費這麼長時間閱讀這篇文章。