關於框架:為了解決VUE的SPA單頁應用對SEO搜尋引擎優化不友好的問題,這幾天一直在調研各種SSR框架。比如doc.ssr-fc.com/ 和 fmfe.github.io/genesis-do 都是比較不錯,且有自己理念和想法的框架。但是對於公司來說技術規範差異太大,團隊學習成本比較高,思來想去,還是基於NUXT.JS自己搭建一套SSR框架慢慢完善吧。
關於本檔案:本檔案是從官網檔案中摘錄的一些重點內容,以及加入了自己的一些調整和對官網內容的理解和解釋。
關於官網:NUXT中文網 特別適合新手學習,檔案及案例十分清楚詳盡,可以說有手就行。但是,中文網的更新不及時,有些章節(比如fetch勾點中不能使用this)甚至存在明顯錯誤,所以有一定技術水平的寶子,建議直接檢視 NUXT英文官網 。
SSR
技術(即伺服器端渲染
技術),區別於原先純Vue框架的SPA
應用(即單頁應用
)。SPA
應用只有一個index.html的入口檔案,頁面顯示的所有內容均靠使用者端JS進行渲染,對於搜尋引擎(SEO
)優化來說,整個網站只有一個空頁面,十分不友好。而伺服器端渲染
技術,是藉助node.js
作為框架伺服器端,在初次存取一個頁面的時候,先在伺服器端預請求介面,並在伺服器端組裝完成的html頁面後,返回給使用者端呈現。Nuxt.js
是基於Vue
框架的一款伺服器端渲染
框架,提供了特有的框架結構和伺服器端渲染宣告週期。Node.js+Webpack+vue+Nuxt.js
進行開發,提供ElementUI
作為UI框架。開發前需全域性安裝Node.js
與webpack
開發環境。Node.js
版本為v16.15.0
,最低版本不得低於12
,推薦安裝nvm
或n
等node版本管理工具。master
分支只用於拉取框架程式碼,xxx_dev
為開發分支,xxx_test
為開發分支,xxx
為生產分支。# 安裝框架以來
$ npm install
# 啟動本地開發環境,預設埠號:3000
$ npm run dev
# 編譯並在生產環境啟動
$ npm run build
$ npm run start
# 將網站打包成靜態化頁面
$ npm run generate
-- 框架根目錄
-- .nuxt Nuxt運營和編譯自動生成
-- dist 執行Nuxt靜態化時生成
-- api 全域性通用的Api請求函數(非Nuxt提供)
-- assets 靜態資源目錄,存放全域性css、image等
-- components 自定義元件目錄,此目錄下元件無需引入,按需使用即可
-- layout 佈局檔案,參考https://www.nuxtjs.cn/guide/views
-- middleware 中介軟體,類似於路由守衛
-- modules 模組,用於設定全域性監聽等,參考https://www.nuxtjs.cn/guide/modules
-- pages 頁面目錄,Nuxt會根據此目錄自動生成路由,參考https://www.nuxtjs.cn/guide/routing
-- plugins 外掛目錄,自定義各種外掛,參考https://www.nuxtjs.cn/guide/plugins
> global.js (全域性變數與全域性方法)
> plugin.js (全域性引入第三方元件)
> request.js (全域性請求封裝)
> filter.js (全域性過濾器封裝)
> util.js (全域性工具函數封裝)
> all.client.js(僅在使用者端執行外掛,暫時替代原app.vue)
-- static 不需要webpack編譯的靜態檔案,一般存放ico等檔案
-- store Vue狀態樹,與原寫法有所不同,參考https://www.nuxtjs.cn/guide/vuex-store
-- utils 工具類包 (非Nuxt提供)
.editorconfig
.gitignore
env.js 環境變數設定,分dev、test、pro三種環境
nux.config.js Nuxt的所有設定項,參考https://www.nuxtjs.cn/api/configuration-build
package-lock.json
package.json
README.md 框架使用檔案
ReleaseNote.md 版本更新說明
-- Nuxt完整宣告週期
【伺服器端渲染】
-- 全域性
nuxtServerInit 第一個:nuxt中第一個執行的生命週期
RouteMiddleware 第二個:中介軟體,類似於原框架的路由導航守衛
-- 元件
validate 是用來校驗url引數符不符合
asyncData Nuxt專屬宣告週期,可用於資料請求,只有page可用,子元件內部不可用
beforeCreate Vue宣告週期,但是伺服器端會執行(不可用於資料請求,資料請求相關操作會在使用者端執行)
created Vue宣告週期,但是伺服器端會執行(同上)
fetch Nuxt專屬宣告週期,可用於資料請求, page和子元件都可用
【使用者端渲染】
-- 全域性
* `@/plugins/all.client.js` (並非Nuxt宣告週期,是隻在使用者端執行的外掛。此框架中用於暫時替代原框架中在App.vue中進行的全域性初始化操作。)
-- 元件
beforeCreate
created
beforeMount
mounted
... (其他Vue後續宣告週期)
beforeCreate/created
是Vue的生命週期,但是會在伺服器端和使用者端各執行一次,但這兩個勾點,僅供瞭解,不能用於資料請求。asyncData
和fetch
都是Nuxt提供的宣告週期,都可用於資料請求。只是寫法略有不同(參考後續章節【五、資料請求】)。@/plugins/all.client.js
並非Nuxt宣告週期,是隻在使用者端執行的外掛。但是Nuxt
框架去掉了app.vue
,此外掛的宣告週期,近似於原來的app.vue
,故暫時用於替代原框架中在App.vue中進行的全域性初始化操作(是否恰當暫時不知)。asyncData
和fetch
都是Nuxt提供的宣告週期,都可用於資料請求,都會在伺服器端預請求資料進行組裝;asyncData
只能在pages
級別的頁面中呼叫,在子元件內部不能呼叫;fetch
則可以同時在頁面和子元件中呼叫;asyncData
,但為了保持與老框架寫法的一致,本框架暫時建議採用fetch
(後果未知)fetch
請求相比於asyncData
的已知缺陷有:
fetch
請求時,可明顯看到瀏覽器索引標籤的title出現一瞬間undefined
beforeCreate/created
也可以在伺服器端渲染,但是這兩個勾點的資料請求操作只會在使用者端執行,非特殊情況,切勿用於頁面初始化。// ① 使用return返回的物件,將直接初始化到元件`data`中
async asyncData({app, params}) {
const { code, data } = await app.$get('/policy/findById/'+params.id)
return {detail: data}
},
// ② return一個Promise,將在Promise執行完成後,將資料初始化到元件`data`中
asyncData({app, params}) {
return app.$get('/policy/findById/'+params.id).then(res => {
return {detail: data}
})
},
// ③ 第二個引數為callback回撥函數,可直接傳入資料,初始化到元件`data`中
asyncData({app, params}, callback) {
app.$get('/policy/findById/'+params.id).then(res => {
callback(null, {detail: data})
})
},
// ① 使用return返回一個Promise
fetch() {
return this.getDetail()
},
// ② 使用await/async
async fetch() {
await this.getDetail()
},
methods: {
// ① 使用await編寫methods方法
async getDetail(id){
const { code, data } = await this.$get('/policy/findById/'+this.$route.params.id)
this.detail = data
}
// ② 使用return Promise編寫methods方法
getDetail(id){
return this.$get('/policy/findById/'+this.$route.params.id).then(resw => {
this.detail = res.data
})
}
}
$request/$get/$post
掛在到vue根範例,建議直接只用this
或上下文context.app
呼叫// 以this呼叫為例,如果是在`asyncData`中,需要使用上下文`context.app`呼叫
// ① get
this.$get('/policy/findById/'+this.$route.params.id)
// ② post
this.$post('/policy/findAll/',{page:1,size:10,params:{}})
// ③ request
this.$request({
url: '/policy/findAll/',
method: 'post',
data: {page:1,size:10,params:{}}
})
五 2.1
的方式呼叫,但是也相容了老框架的api分離式呼叫,用於提取可複用的公共請求
。@/api/*.js
管理。/**
* @/api/index.js
*/
import request from '@/utils/request'
export function getPageList(data) {
return request.post('/policy/findAll', data)
}
/**
* @/pages/index.vue
*/
import { getPageList } from "@/api/index.js"
export default {
fetch() {
return this.getPageList(this.pageDto)
},
methods: {
getPageList(pageDto) {
return getPageList(pageDto).then(res => {
this.pageList = res.data.result
})
}
},
}
asyncData
或fetch
)中進行,極個別無法在伺服器端渲染的請求,可以在Vue的生命週期(created
或mounted
)中初始化;asyncData/fetch
),不能使用任何瀏覽器專屬的對像(如DOM
物件),也就是document
和window
,以及window
的各種物件和方法,例如setTimeout
、setInterval
、localStorage
、sessionStorage
等;created
或mounted
中初始化。pages
約定式路由
,即不再使用route.js
進行路由宣告,而是由框架根據pages
目錄自動生成路由,詳見路由_
開頭,表示此為動態路由,可以傳入不同引數,在元件內容,可以使用上下文或者this.$router取到路由引數;
/pages/news/detail/_id.vue
、/pages/news/detail/_id/index.vue
http://domain.com/pages/news/detail/12345
(上述兩種寫法均為這一路徑)_id.vue
的寫法,表示id
為可選引數,即可以通過http://domain.com/pages/news/detail
存取。如果要對id進行限制或驗證,可以在元件內使用validate()
驗證;/_id/index.vue
的寫法,表示id
為必選引數,存取http://domain.com/pages/news/detail
會報404。如果只要求id必填,而沒有其他格式限制,可以使用此方式。validate()
驗證範例// return true表示驗證通過,return false表示驗證失敗 404
validate({ params }) {
return /^\d+$/.test(params.id)
},
plugins
nuxt.config.js
中引入外掛即可,類似於原框架main.js
相關功能。詳見外掛頂部註釋
):
plugin.js
用於全域性引入各種npm包;global.js
用於宣告全域性變數與全域性方法;request.js
實現了全域性請求封裝(對應@/utils/request.js
);filter.js
實現了全域性請求封裝(對應@/utils/filter.js
);util.js
實現了全域性請求封裝(對應@/utils/util.js
);all.client.js
只在使用者端引入,用於替代原框架中app.vue
中的各種初始化操作;*.js
表示伺服器端使用者端均匯入;*.client.js
表示僅在使用者端匯入;*.server.js
表示只在伺服器端匯入;layout
default.vue
,預設所有頁面都將呼叫;error.vue
是錯誤檢視,當頁面出現問題時,自動呼叫;export default {
// 需要呼叫的檢視名稱,不寫預設呼叫default.vue
layout: 'onlyBody',
data(){
return {}
}
}
components
fetch
(子元件中不支援asyncData
)。components
中宣告的各種元件,在使用時,無需import
匯入。直接使用元件名按需呼叫即可。<template>
<div>
// Header元件
<Header />
</div>
</template>
store
store
資料夾為Nuxt提供用於定義Vuex狀態樹的資料夾,詳細檔案參照:Vuex狀態樹。index.js
對應$store.state.xxx
,而user.js
對應$store.state.user.xxx
store
中模組的定義與普通Vue框架大體相同,只是Nuxt框架會自動參照Vuex並載入到構建設定重,無需我們自己new Vuex()
/**
* 【注意區別】
* state mutations action不再是包裹在一個物件中,並在new Vuex()的時候傳入。 而是分別作為單獨模組使用export匯出即可。
*/
export const state = () => ({
counter: 0
})
export const mutations = {
increment(state) {
state.counter++
}
}
middleware
middleware
是框架中用於宣告中介軟體的資料夾,宣告後在nuxt.config.js
中設定中介軟體即可,詳細檔案參照:中介軟體。@/middleware/router.js
為已經升級宣告好的路由守衛中介軟體
,可替代原框架中router.beforeEach
中的路由守衛功能;modules
@/modules/generator.ts
實現了一個對靜態化結束generate:done
時進行監聽並處理的範例。const generator: any = function () {
this.nuxt.hook('generate:done', (context: any) => {
// TODO samething
})
}
export default generator
this.nuxt.hook('generate:done',() => {})
的Nuxt框架hooks
還有很多,例如:ready
、error
、render:before
、build:compile
等等……詳細參見INTERNALSQ&A
head
設定title
,必要時還需在詳情頁設定description
。(!!!切記!!!)export default {
head() {
return {
// title必須設定!!! 列表可以直接寫「xxx列表」,詳情頁等有不同標題的,要用新聞標題、商品標題等作為title字首。
title: this.detail.title + '_新聞詳情',
meta: [
// 詳情頁,需要設定不同的description。 this.$getDesc 為全域性封裝的從富文字中擷取100字元的description
{ hid: 'description', name: 'description', content: this.$getDesc(this.detail.details) },
],
}
}
}
pages
目錄中的層級結構,務必按照功能梳理清楚,比如「news(新聞)」
的列表、詳情都要在一個資料夾中。(!!!目錄結構一旦確定,原則上不可再調整!!!)
CSS
篇】!!css
檔案,位於@/assets/css/
中。框架推薦使用scss
語言,使用"sass": "~1.32.13"
進行編譯;common.scss
為全域性公共CSS,請將全域性樣式表宣告於此。或自行定義CSS檔案,並在此檔案中import
匯入;font.scss
用於定義本框架各種字型、圖示庫等;variables.scss
宣告了框架的各種全域性Scss變數,可以在所有頁面使用。
$mainColor
表示,不要在各自檔案中自行宣告!element-variables.scss
是ElementUI的主題宣告檔案,如需全域性調整ElementUI的配色,請在此調整;