Nuxt.js踩坑記,利用Nuxt一鍵生成多頁面靜態站點

Nuxt.js踩坑記,利用Nuxt一鍵生成多頁面靜態站點

本文結合實際項目基於Nuxt.js@1.4.0進行講解,部分案例摘自 Nuxt.js 官方文檔,如需轉載,請標明出處。

  • Nuxt.js簡單介紹
  • 為什麼使用Nuxt.js?
  • 項目創建
  • 項目配置
    • nuxt.config.js總覽
    • 路由(router)
    • Nuxt模塊(modules)
    • 插件(plugins)
    • 頁面元信息(head)
  • 頁面布局(layout)
  • 狀態管理(vuex)
  • 一鍵靜態化

Nuxt.js簡單介紹

2016 年 10 月 25 日,zeit.co 背後的團隊對外發布了 Next.js,一個 React 的服務端渲染應用框架。幾小時後,與 Next.js 異曲同工,一個基於 Vue.js 的服務端渲染應用框架應運而生,我們稱之為:Nuxt.js。

Nuxt.js 是一個基於 Vue.js 的通用應用框架。通過對客戶端/服務端基礎架構的抽象組織,Nuxt.js 主要關注的是應用的 UI渲染。Nuxt.js 預設了利用Vue.js開發服務端渲染的應用所需要的各種配置。

為什麼使用Nuxt.js?

  • SSR(服務端渲染)的頁面初始載入時間顯然優於單頁首屏渲染
  • 可以方便的對 SEO 進行管理
  • 無需配置頁面路由,內置 vue-rouer,自動依據 pages 目錄結構生成對應路由配置
  • 便捷的 HTML 頭部標籤管理(vue-meta)
  • 項目結構自動代碼分層
  • 支持靜態化(本文將著重以此展開介紹)

項目創建

為了便於大家快速使用,Nuxt.js 提供了很多模板

starter-template: 基礎Nuxt.js模板

typescript-template: 基於Typescript的Nuxt.js模板

express-template: Nuxt.js + Express

koa-template: Nuxt.js + Koa

adonuxt-template: Nuxt.js + AdonisJS

electron-template: Nuxt.js + Electron

...

等等,更多的可以在這裡看到 nuxt-community

這裡我們使用 starter-template,可以使用 vue-cli 安裝:

$ npm install -g vue-cli$ vue init nuxt-community/starter-template nuxt-demo$ cd nuxt-demo$ npm install

生成項目結構如下:

nuxt-demo/├── assets/├── components/│ └── AppLogo.vue├── layouts/│ └── default.vue├── middleware/├── pages/│ └── index.vue├── plugins/│ └── README.md├── static/│ └── favicon.ico├── store/├── nuxt.config.js├── package.json└── README.md

可以看出來項目結構還是比較清晰的,接著我們根據業務需求在 vue-cli 腳手架生成的項目基礎上擴展和修改出來的目錄結構如下(已隱去部分文件):

nuxt-demo/├── api/ //- 介面│ └── index.js├── assets/ //- 需要編譯的靜態資源,如 scss、less、stylus│ ├── images/ //- 圖片│ └── styles/ //- 樣式├── build/ //- 自定義的一些編譯配置├── components/ //- 公用的組件│ ├── dm-toast.vue //- 全局組件`dm-toast`│ └── ...├── data/ //- 靜態數據├── layouts/ //- 布局│ ├── components/│ │ ├── dm-footer.vue //- 公用header│ │ └── dm-header.vue //- 公用footer│ └── default.vue //- 默認布局├── middleware/ //- 中間件├── mixins/ //- Vue mixins├── pages/ //- 頁面│ ├── index.vue //- 主頁│ └── ...├── plugins/ //- vue插件│ └── dm-tracker.js/ //- 掛載utils/tracker.js├── static/ //- 無需編譯處理的靜態資源│ └── images/ //- 這裡存放了一些通過數據循環出來的圖片├── store/ //- vuex│ └── index.js├── utils/ //- 工具集│ ├── index.js│ ├── http.js //- axios│ ├── tracker.js //- PV統計│ └── tracker-uitl.js├── vendor/ //- 第三方的庫和插件│ └── index.js├── nuxt.config.js //- Nuxt.js配置文件├── seo.config.js //- SEO相關配置文件├── package-lock.json //- npm的版本鎖├── package.json└── README.md

項目配置

Nuxt.js 默認的配置涵蓋了大部分使用情形,可通過 nuxt.config.js 來覆蓋默認的配置,下面相關配置根據實際項目驅動講解,未涉及到的配置項可查閱 Nuxt.js 文檔。

nuxt.config.js 總覽

module.exports = { //- Document Common <head> head: { meta: [ title: 我是一個title, { charset: utf-8 }, { name: viewport, content: width_=device-width, initial-scale=1 }, { name: renderer, content: webkit }, { name: applicable-device, content: pc }, { http-equiv: X-UA-Compatible, content: IE=edge,chrome=1 }, { http-equiv: Cache-Control, content: no-transform }, { http-equiv: Cache-Control, content: no-siteapp } ], link: [ { rel: icon, type: image/x-icon, href: 你的icon地址 } ], //- 這裡可以寫一些每個頁面需要額外引入的一些js代碼,比如:百度統計 //- `alert(1)` 僅為代碼示例 script: [{ type: text/javascript, innerHTML: `alert(1)` }], //- __dangerouslyDisableSanitizers 設置<script>中的內容不被轉義。 //- https://github.com/declandewet/vue-meta#__dangerouslydisablesanitizers-string __dangerouslyDisableSanitizers: [script] } //- 頁面切換的時候進度條的顏色 loading: { color: #77b6ff }, //- modules 可以用來擴展核心功能或者添加一些集成 //- 這裡使用了一個本地開發請求遠端介面的反向代理模塊 `@nuxtjs/proxy` //- https://nuxtjs.org/api/configuration-modules modules: [ @nuxtjs/proxy ], //- 上面 modules 中配置了 @nuxtjs/proxy 時,此欄位才會生效 //- https://github.com/nuxt-community/proxy-module proxy: { /api: http://xxx.xxx.com }, //- 在這裡註冊 `Vue` 的插件、全局組件或者其他的一些需要掛載到 `Vue` 原型下面的東西 //- ssr 為 `false` 表示該文件只會在瀏覽器端被打包引入 //- https://nuxtjs.org/api/configuration-plugins plugins: [ ~plugins/dm-toast, { src: ~plugins/dm-tracker, ssr: false } ], //- 配置全局樣式文件(每個頁面都會被引入) //- `lang` 可以為該樣式文件配置相關 loader 進行轉譯 css: [ animate.css, { src: ~assets/styles/common.scss, lang: scss } ], //- 配置 Nuxt.js 應用生成靜態站點的具體方式。 //- https://nuxtjs.org/api/configuration-generate generate: { //- 為動態路由添加靜態化 //- 靜態化站點的時候動態路由是無法被感知到的 //- 所以可以預測性的在這裡配置 routes: [ /1, /2, /3 ... ] }, //- router 屬性讓你可以個性化配置 Nuxt.js 應用的路由(vue-router) //- https://nuxtjs.org/api/configuration-router router: { //- 中間件在每次路由切換前被調用 middleware: set-env, //- 通過 extendRoutes 來擴展或者修改 Nuxt.js 生成的路由表配置 extendRoutes(routes) {} }, //- 編譯配置 build: { //- 使用 webpack-bundle-analyzer 分析並可視化構建後的打包文件 //- 你可以基於分析結果來決定如何優化它 analyze: true, //- 為客戶端和服務端的構建配置進行手動的擴展處理 //- https://nuxtjs.org/api/configuration-build#extend extend(config, { isDev, isClient, isServer }) { if (isDev && isClient) { //- 使用 ESLint 保證代碼規範 config.module.rules.push({ enforce: pre, test: /.(js|vue)$/, loader: eslint-loader, exclude: /(node_modules)/ }) } if (!isDev) { config.module.rules //- 覆蓋默認 `url-loader` 配置 .find((rule) => rule.loader === url-loader) .options.name = images/[name].[ext]?v=[hash:7] } }, //- 這裡可以自定義打包後的文件名 //- `hash` 項目中任何一個文件改動後就會被重新創建 //- `chunkhash` 是根據模塊內容計算出的hash值,對應的文件發生內容變動就會重新計算 //- 生成如下: //- <head> //- ... //- <link href="//cdn.xxx.com/manifest.js?v=8d09730" rel="preload" as="script"> //- <link href="//cdn.xxx.com/vendor.js?v=8d09730" rel="preload" as="script"> //- <link href="//cdn.xxx.com/app.js?v=fea3ec0" rel="preload" as="script"> //- <link href="//cdn.xxx.com/pages_index.js?v=6f7b904" rel="preload" as="script"> //- ... //- </head> filenames: { manifest: js/manifest.js?v=[hash:7], vendor: js/vendor.js?v=[hash:7], app: js/app.js?v=[chunkhash:7], //- `chunk` 這裡這樣使用編譯會報錯,最後面會講解相關解決方案 chunk: js/[name].js?v=[chunkhash:7] }, //- 自定義 postcss 配置 //- https://nuxtjs.org/api/configuration-build#postcss postcss: [ require(autoprefixer)({ browsers: [> 1%, last 3 versions, not ie <= 8] }) ], //- 這裡可以設置你的CDN地址,生成的靜態資源將會基於此CDN地址加上URL前綴 publicPath: //cdn.xxx.com/, //- Nuxt.js 允許你在生成的 vendor.js 文件中添加一些模塊,以減少應用 bundle 的體積 //- 這裡說的是一些你所依賴的第三方模塊 (比如 axios),或者使用頻率較高的一些自定義模塊 //- https://nuxtjs.org/api/configuration-build#vendor vendor: [ axios, ... ] }}

路由(router)

Nuxt.js 依據 pages 目錄結構,自動生成 vue-router 模塊的路由配置。

比如:

├── pages/│ ├── b-case/│ │ ├── home.vue│ │ ├── home/│ │ │ └── _type.vue│ │ └── _id.vue│ └── index.vue

生成路由配置如下:

[ { name: b-case-home, path: /b-case/home, component: E:\\nuxt-demo\\pages\\b-case\\home.vue, chunkName: pages/b-case/home, children: [{ name: b-case-home-type, path: :type?, component: E:\\nuxt-demo\\pages\\b-case\\home\\_type.vue, chunkName: pages/b-case/home/_type }] }, { name: b-case-id, path: /b-case/:id?, component: E:\\nuxt-demo\\pages\\b-case\\_id.vue, chunkName: pages/b-case/_id }, { name: index, path: /, component: E:\\nuxt-demo\\pages\\index.vue, chunkName: pages/index }]

如果你想修改已有路由配置可以在 nuxt.config.js 中添加 route.extendRoutes 配置項,覆蓋已有路由或者添加新的路由:

module.exports = { ... router: { ... extendRoutes(routes) { //- `routes` 是一個包含所所有路由配置信息的參數 } }}

Nuxt模塊(modules)

如果你對上述路由的配置方式不滿意,想要更加個性化的自定義路由,Nuxt.js 社區提供了一款 Nuxt模塊 router-module

使用步驟:

首先安裝這個模塊

npm install @nuxtjs/router --save # OR yarn add @nuxtjs/router

然後在 nuxt.config.js 中添加模塊名 到 modules 欄位下:

module.exports ={ ... modules: [ @nuxtjs/router ]}

在項目根目錄下創建 routes.js,並 export 一個方法 createRouter。這裡有一點要記住,不能使用 pages/ 這個目錄,會和 Nuxt.js 官方的路由機制產生衝突。

import Vue from vueimport Router from vue-routerimport Home from ../views/home.vueVue.use(Router)export function createRouter() { return new Router({ mode: history, routes: [ { path: /, component: Home } ] })}

其他比較常用的 Nuxt.js 模塊:

axios-module:axios模塊,它將網路請求與頁面的進度條集成

proxy-module:反向代理模塊,本地便捷調試遠端介面

auth-module:鑒權模塊

python-module:用 Python 編寫 Nuxt.js 應用程序

pwa-module:把你的站點變成 pwa 漸進式網路應用

配置方法比較類似,這裡不作詳細講解

插件(plugins)

插件可以讓我們向 Vue 注入一些使用率比較高的屬性或者方法,這裡我們講解一個埋點的插件是如何實現的。

在講解埋點之前我們需要先了解一下PV的概念:PV(page view),即頁面瀏覽量,或點擊量,PV之於網站,就像收視率之於電視。

通過 plugins 配置項,我們可以輕而易舉的在 Vue 中使用插件。同時我們需要在 plugins 目錄下創建對應的文件,以保證配置項可以正確的載入這個文件。

plugins/dm-tracker.js

//- 將發起pv統計的方法掛載到 Vue 原型下//- 讓每個組件都能通過 `this.$tracker` 訪問import Vue from vueimport { trackerPlugins } from ../utils/tracker.jsVue.use(trackerPlugins)

utils/tracker.js 里是一些發起 PV 統計所調用的代碼(隱去了部分業務代碼)。

utils/tracker.js

.../** * 發起PV統計 * @param {String} caFrom - ev: @ca_from * @param {Object} vueRouteName - vm.$route.name * @return {Promise} 成功`then`或者失敗`catch`的回調 */const tracker = (caFrom, vueRouteName) => { ...}export default trackerexport const trackerPlugins = { install(Vue, options) { Vue.prototype.$tracker = tracker }}

我們發現幾乎每次初始化進入或者跳轉到一個新的頁面都需要調用 this.$tracker 這個方法,要是在每個頁面文件里加豈不是很麻煩,況且如果將埋點深入到業務層後期維護更新不免產生一些不必要的繁瑣,所以我們在 layout/default.vue 裡面對 $route 進行監聽,同時設置 watch 參數 immediate: true,便可針對每個頁面實現這個功能。

layout/default.vue

... mounted() { //- 通過監聽路由變化,得到不同頁面的 pv 統計參數,同時 `immediate: true` 使得每次頁面初始進來也會默認執行一次 this.$watch($route, ({ name }) => this.$tracker(-, name), { immediate: true }) }...

至此,就完成了一個 PV 統計插件。

頁面元信息(head)

Nuxt.js 文檔是這麼說的:

使用 head 方法可以設置當前頁面的頭部標籤,在 head 方法里可通過 this 關鍵字來獲取組件的數據,所以你可以利用頁面組件的數據來個性化設置 meta 標籤。為了避免子組件中的 meta 標籤不能正確覆蓋父組件中相同的標籤而產生重複的現象,建議利用 hid 鍵為 meta 標籤配一個唯一的標識編號。請閱讀關於 vue-meta 的更多信息。

官方示例:

<template> <h1>{{ title }}</h1></template><script>export default { data () { return { title: Hello World! } }, head () { return { title: this.title, meta: [ { hid: description, name: description, content: My custom description } ] } }}</script>

根據官方文檔的描述,我們了解到頁面里的 head 配置優先順序高於 nuxt.config.js 中的 head,就是說同等的配置會覆蓋 nuxt.config.js 中的 head 相關位置的配置。但是這個等同覆蓋的條件是你為它設置了同一個 hid,它會以此作為等同替換的條件去查找相關 dom 元素進行替換。

因為項目生成的是多頁面靜態站點,很多頁面需要配置的 meta 多少有些不一樣,深入到每一個頁面去寫單獨的配置信息不免繁瑣了許多,所以我們可以在 nuxt.config.jshead 欄位中將公用的一些 head 信息放在裡面;所以我們是不是可以把它單獨抽離出來作為一個配置文件,並和每個頁面的路由名字($route.name)關聯起來,這樣按照理想格式的 seo.config.js 就誕生了。

seo的配置文件寫好了,接下來我們應該怎麼才能注入這個配置呢,很簡單,只需要在 default.vuehead 欄位下將不同頁面的配置,將他們關聯起來。這樣就達到了通過每個頁面的路由名字($route.name)來映射和渲染對應的 meta。

layout/default.vue

<template> <div> <dm-header/> <nuxt/> <dm-footer/> </div></template><script>import DmHeader from ./components/dm-headerimport DmFooter from ./components/dm-footerconst heads = seo => function getHeadsMap() { const map = {} for (const key in seo) { map[key] = seo[key].head } return map}const routeMapHead = heads(require(../seo.config))export default { components: { DmHeader, DmFooter }, computed: { routeMapHead }, head() { //- SEO 的中心化管理, 根據路由 `$route.name` 映射 Document <head> const route = this.$route const head = this.routeMapHead[route.name] return typeof head === function ? head(route) : head }}</script>

seo.config.js

//- 根據路由 `$route.name` 映射配置//- path - 頁面的訪問路徑//- head - Document <head>,頁面的元信息module.exports = { index: { head: { title: 我是首頁, meta: [ { hid: keywords, name: keywords, content: }, { hid: description, name: description, content: }, { name: mobile-agent, content: format=wml; url=//該頁面對應的移動端網址/ }, { name: mobile-agent, content: format=xhtml; url=//該頁面對應的移動端網址/ }, { name: mobile-agent, content: format=html5; url=//該頁面對應的移動端網址/ } ], link: [ { rel: alternate, type: applicationnd.wap.xhtml+xml, media: handheld, href: //該頁面對應的移動端網址/ } ] } }, //- head 可以是一個 `function` //- `function` 中會接收過來一個參數 `route`,表示當前頁面的路由信息 //- 可以根據這個做一些動態的配置信息 //- 比如動態路由生成的頁面中的 meta 信息,可能會根據頁面內容來決定 name: { head(route) { const titles = { 1: ofo小黃車, 2: 盒馬生鮮, 3: 順豐速運 } return { title: `${titles[route.params.id]}_客戶案例-斗米網`, meta: [ { name: mobile-agent, content: `format=wml; url=//該頁面對應的移動端網址${route.fullPath}` }, { name: mobile-agent, content: `format=xhtml; url=//該頁面對應的移動端網址${route.fullPath}` }, { name: mobile-agent, content: `format=html5; url=//該頁面對應的移動端網址${route.fullPath}` } ], link: [ { rel: alternate, type: applicationnd.wap.xhtml+xml, media: handheld, href: `//該頁面對應的移動端網址${route.fullPath}` } ] } } } ...}

頁面布局(layout)

Nuxt.js 中,抽象出來一個新的概念:layout,這樣將頁面劃分為三層:1. layout、2. page、3. component,很方便的在多種布局方案中切換。

layout與page與component對應關係

在頁面 pages/*.vue 文件中可以指定一種布局,不指定的時候會使用默認布局 default

比如以下目錄結構:

├── layouts/ //- 布局│ ├── components/│ │ ├── dm-footer.vue //- 公用header│ │ └── dm-header.vue //- 公用footer│ ├── box.vue│ └── default.vue //- 默認布局├── pages/ //- 頁面│ └── index.vue

使用 box.vue 的布局,<nuxt/> 對應頁面部分,類似 Vue 的 slot

layouts/box.vue

<template> <div> <dm-header/> <nuxt/> <dm-footer/> </div></template><script>import DmHeader from ./components/dm-headerimport DmFooter from ./components/dm-footerexport default { components: { DmHeader, DmFooter }}</script>

pages/index.vue

<script>export default { layout: box ...}</script>

狀態管理(vuex)

像普通的 Vue 應用一樣,在 Nuxt.js 中也可以使用 vuex,而且無需額外 npm install vuex --save 和配置,只要直接在項目根目錄創建 store 文件夾,Nuxt.js 會自動去尋找下面的 .js 文件,並自動進行狀態樹的模塊劃分。

Nuxt.js 支持兩種使用 store 的方式:

普通方式:返回一個 Vuex.Store 實例,感覺很眼熟有木有

store/index.js

import Vue from vueimport Vuex from vuexVue.use(Vuex)const store = () => new Vuex.Store({ state: { counter: 0 }, mutations: { increment (state) { state.counter++ } }})export default store

模塊方式:store 目錄下的每個 .js 文件會被轉換成為狀態樹指定命名的子模塊,index.js 會被作為根模塊

store/index.js

export const state = () => ({ counter: 0})export const mutations = { increment (state) { state.counter++ }}

store/todos.js

export const state = () => ({ list: []})export const mutations = { toggle (state, todo) { todo.done = !todo.done }}

最終渲染出來的狀態樹:

new Vuex.Store({ state: { counter: 0 }, mutations: { increment (state) { state.counter++ } }, modules: { todos: { state: { list: [] }, mutations: { toggle (state, { todo }) { todo.done = !todo.done } } } }})

一鍵靜態化

使用下面的命令生成應用的靜態目錄和文件:

npm run generate

然後會在項目根目錄生成 dist 目錄,靜態化後的資源都在其內(html、js、css...)

當然,在靜態化的時候還是遇到了一些問題:我們有一個專門放置靜態資源的 CDN 分發伺服器,所以每次版本更新的時候需要版本智能更新(不然用戶訪問到的可能就是舊的資源),即對應的模塊內容發生改變時才去更新這個版本號,雖然可以通過 js/[name].[chunkhash:7].js 這樣的配置實現,但是 CDN 訪問到的靜態資源是通過 git 控制上線的,所以這樣生成的靜態資源,文件名每次可能不一樣,這樣就會越來越多,git 需要定時清理,比較麻煩。於是才有了這樣一個兩全的方案:既控制了版本更新,也讓文件名不產生變動。

就是如下配置:

module.exports = { ... build: { ... filenames: { ... chunk: js/[name].js?v=[chunkhash:7] } }}

想法很豐滿,現實很骨感,在 nuxt.config.js 中使用這樣的配置靜態化編譯的時候會報錯 Cannot find module pages_index.js?v=6f7b904...,(⊙o⊙)…經過一系列的源碼追蹤發現是vue-server-renderer 這個模塊的 BUG。

雖然向官方提了相關 issue ,但是因不可抗拒的因素暫時無法修復。

vue-server-renderer/server-plugin.js 78行 asset.name.match(/.js$/) 這個判斷里的正則明顯把我們設定的格式 js/[name].js?v=[chunkhash:7] 過濾掉了,不會走這個判斷,所以我們只要想辦法把這個判斷條件改一下,讓它走這裡。發現他同文件上面有個 isJS 函數,這樣就省事多了,我們可以直接調用。

所以我們如果改源碼的話只需要把這裡 asset.name.match(/.js$/) 替換為 isJS(asset.name) 就行了。但是改源碼總歸不好,因為並不能保證團隊其他成員的機器上模塊庫一致。投機取巧,既然直接修改不好,那就間接修改唄。

開始動手,先安裝一個 npm 的模塊:

npm install shelljs -D # OR yarn add shelljs -D

接著在項目下新建兩個文件:

  • build/nuxt-generate.js:用來執行靜態化的一些命令
  • build/vue-server-renderer.patch.js:給 vue-server-renderer 模塊打補丁

build/nuxt-generate.js

const shell = require(shelljs)const { resolve } = require(path)const nuxt = resolve(__dirname, ../node_modules/.bin/nuxt)const logProvider = require(consola).withScope(nuxt:generate)shell.exec(`npm run patch`, (code, stdout, stderr) => { if (code !== 0) { logProvider.error(stderr) } //- 上面的命令執行成功之後在執行下面的命令 shell.exec(`${nuxt} generate`)})

build/vue-server-renderer.patch.js

const { resolve } = require(path)const fs = require(fs)const SSRJSPath = resolve(__dirname, ../node_modules/vue-server-renderer/server-plugin.js)const consola = require(consola)const logProvider = consola.withScope(vue:patch)module.exports = VueSSRPatch()/** * 對 `vue-server-renderer/server-plugin.js` 源碼內容進行替換 * asset.name.match(/.js$/) * => * isJS(asset.name) */function VueSSRPatch() { //- 檢測該模塊是否存在 if (fs.existsSync(SSRJSPath)) { let regexp = /asset.name.match(/\.js$/)/ let SSRJSContent = fs.readFileSync(SSRJSPath, utf8) //- 檢測是否存在需要替換的內容(通常是指該項目在本機第一次運行) if (regexp.test(SSRJSContent)) { logProvider.start(`發現vue-server-renderer模塊,開始執行修補操作!`) SSRJSContent = SSRJSContent.replace(regexp, isJS(asset.name)) fs.writeFileSync(SSRJSPath, SSRJSContent, utf8) logProvider.ready(`修補完畢!`) return true } logProvider.warn(`該模塊已修補過,無需再次修補,可直接運行`npm run dev` 或 `npm run gen``) return false } logProvider.warn(`未發現該模塊,跳出本次修復!`) return false}

最後在 package.jsonscripts 添加 genpatch 兩條命令:

"scripts": { "dev": "nuxt", "generate": "nuxt generate", "patch": "node build/vue-server-renderer.patch", "gen": "node build/nuxt-generate" }

patch:本機第一次運行或者更新相關模塊(vue-server-renderer)時需要執行一次。

gennpm run patchnpm run generate 的合併命令,就是說會先後執行這兩個,方便本機第一次使用。如果本機執行過 npm run patch 可直接 npm run generate,生成相關靜態頁。

本人文筆拙劣,如有描述不當,歡迎各路大神拍磚!

原文出處:Nuxt.js踩坑記,利用Nuxt一鍵生成多頁面靜態站點

推薦閱讀:

如何評價vue伺服器渲染工具 nuxt.js?

TAG:Nuxtjs | Vuejs | 服務端渲染 |