Nuxt 系列 - #4 自訂 HTML 模板、Header、佈局與 Pages
Nuxt 視圖架構
了解 Nuxt 的構成為何,理解各層可使用的方法與設定,幫助在開發時能夠製作出更符合需求的設計,並且能夠適當分配排版佈局與組件切分。
App Template
Nuxt 預設的模板如下
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
如何自訂 HTML 模板?
於專案的根目錄資料夾新增一個 app.html 的檔案。並調整 html 文件的內容,記得與上述預設模版一樣,將 HTML_ATTRS、HEAD_ATTRS 與 BODY_ATTRS 分別置入 html、head 還有 body 的屬性。
以下為官方提供的範例,添加 IE 的條件表達式
<!DOCTYPE html>
<!--[if IE 9]><html lang="en-US" class="lt-ie9 ie9" {{ HTML_ATTRS }}><![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--><html {{ HTML_ATTRS }}><!--<![endif]-->
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
</body>
</html>
HTML Head
利用 vue-meta 套件,更新 document head 與 meta, vue-meta 套件還有非常多強大的功能,可以花點時間研究。
Nuxt.js 預設 vue-meta 的參數配置如下
{
keyName: 'head', // 設定 meta 資訊在 vue 組件內存在的物件名稱,vue-meta 會使用這個 key 取得設定值
attribute: 'n-head', // vue-meta 在監聽標簽時所添加的屬性名稱
ssrAttribute: 'n-head-ssr', // 讓 vue-meta 知道 meta 已完成伺服器端渲染的屬性名稱
tagIDKeyName: 'hid' // vue-meta 用來決定是否覆蓋或增加 tag 的屬性名稱
}
可在 nuxt.config.js 定義所有 head 預設的標籤(title、meta、link、script),這樣所有的頁面都會套用。
由於文章內容頁需要自行定義 keywords、description 等設定,所以有一部份的 meta 有設定 hid 屬性,讓子組件可以進行覆蓋。
Layouts
功能相當於一般 vue 專案中我們會建立的 App.vue 檔案,用來定義最底層的 html 排版。預設檔案為 layouts/default.vue。
預設的程式碼如下
<template>
<nuxt/>
</template>
Nuxt 另外提供了自定義 Layouts 的方式
- 於 layouts 資料夾內建立 .vue 檔案。
- 建立 pages 時,在 script 中增加 layout 屬性指定為上一步驟建立的 vue 檔案名稱。
以下為官方範例
建立一個 layouts/blog.vue
<template>
<div>
<div>My Blog</div>
<nuxt/>
</div>
</template>
於頁面 pages/posts/index.vue 使用自定義的 layout
<template>
<!-- Your template -->
</template>
<script>
export default {
layout: 'blog'
// page component definitions
}
</script>
錯誤頁面也可自訂,在 layouts 資料夾內建立 error.vue 檔案來客製錯誤頁面。
error.vue 雖然此檔案放置於 layouts 資料夾,但 nuxt 把它視為 page,所以 template 中不需包含 <nuxt/>,並且會在指定的 layout 中 <nuxt/> 區塊呈現(未指定則為預設)。
當應用程式發生 404、500…等錯誤時,就會顯示此錯誤頁面,並且透過 props 將當次錯誤資訊傳遞至 error.vue 頁面內使用。
Nuxt 預設錯誤頁面程式碼:nuxt-error.vue
以下為官方範例
透過 props 的 error 傳遞當次錯誤資訊,提供頁面使用
error 為 Object,預設內含 message、path 與 statusCode,屬性也可透過 context.error 自訂
<template>
<div class="container">
<h1 v-if="error.statusCode === 404">Page not found</h1>
<h1 v-else>An error occurred</h1>
<nuxt-link to="/">Home page</nuxt-link>
</div>
</template>
<script>
export default {
props: ['error']
}
</script>
Pages
每個 page 組件實際上就是 vue 組件,只是增加了一些 Nuxt 特殊的配置項目,讓開發者能夠快速開發通用於 SSR 與 CSR 的應用程式。
<template>
<h1>Hello {{ name }}!</h1>
</template>
<script>
export default {
asyncData (context) {
// called every time before loading the component
// as the name said, it can be async
// Also, the returned object will be merged with your data object
return { name: 'World' }
},
fetch () {
// The `fetch` method is used to fill the store before rendering the page
},
head () {
// Set Meta Tags for this Page
},
// and more functionality to discover
...
}
</script>
Nuxt 為 page 提供的特殊配置項目
- asyncData - 當需要獲取資料並呈現,在初始化組件之前處理非同步資料操作,並且會與組件的 data 合併,此方法第一個參數為當前頁面組件的 context。
- fetch - 在頁面 render 之前將資料寫入 Vuex store,與 asyncData 方法類似,不一樣的是 fetch 不會設置組件的 data,此外 nuxt 2.12 以後的版本(包含2.12)的使用方法與先前有些不同,使用上要特別注意。
- head - 配置目前頁面的 head 標籤。
- layout - 指定在 layouts 目錄中定義的 vue 檔案。
- loading - 定義目前頁面的 loading 效果,也可關閉之後透過 this.$nuxt.$loading.finish() 與 this.$nuxt.$loading.start() 手動控制此效果。
- transition - 指定頁面切換時的轉場特效的名稱,需搭配樣式設定。
- scrollToTop - 設定目前頁面在渲染頁面前是否要將畫面滾動至頂部。
- validate - 驗証動態路由參數的方法,防止非預期的參數值傳入。
- middleware - 在頁面渲染前調用的方法。
實作案例
Blog 網站地圖
以下為針對 HTML Head 的配置說明
目前 blog 的預設配置在 nuxt.config.js,有可能被覆寫 meta 設定加上 hid 屬性
// nuxt.config.js
export default {
head: {
title: '被程式設計的猴子',
meta: [
{ 'http-equiv': 'Content-Type', content: 'text/html; charset=UTF-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' },
{ name: 'author', content: 'MonkeyBinBin' },
{ property: 'fb:app_id', content: config.fbId },
{ hid: 'keywords', name: 'keywords', content: '被程式設計的猴子, Nuxt.js, Bootstrap 4, MonkeyBinBin, blog' },
{ hid: 'description', name: 'description', content: '使用 Nuxt.js、 Bootstrap 4 建立的blog。分享與紀錄一些程式開發的東西。' },
{ hid: 'og:title', property: 'og:title', content: '被程式設計的猴子' },
{ hid: 'og:type', property: 'og:type', content: 'article' },
{ hid: 'og:url', property: 'og:url', content: path.join(config.domain, baseUrl) },
{ hid: 'og:image', property: 'og:image', content: path.join(config.domain, baseUrl, '/img/fb.jpg') },
{ hid: 'og:image:width', property: 'og:image:width', content: '474' },
{ hid: 'og:image:height', property: 'og:image:height', content: '474' },
{ hid: 'og:description', property: 'og:description', content: '使用 Nuxt.js、 Bootstrap 4 建立的blog。分享與紀錄一些程式開發的東西。' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: `${baseUrl}favicon.ico` }
]
}
}
若其他頁面有需要更改預設的 meta 標籤,就在子組件使用"hid"屬性來設定覆蓋。
this.post 為 data 內的資料,從 api 取得資料後會存放於此。
增加 script 設定是為了可以在文章內崁入 codepen。
以下為文章詳細內容頁(pages/article/_id.vue) head 部份程式碼
// /pages/article/_id.vue
export default {
name: 'Article',
head () {
const _head = {
title: this.errorMsg || this.post.title,
meta: [
{ hid: 'og:url', property: 'og:url', content: `${constant.domain}${constant.baseUrl}article/${this.id}/` }
],
script: [
{ src: '//assets.codepen.io/assets/embed/ei.js' }
]
}
if (this.post && this.post.tags) {
_head.meta.push({ hid: 'keywords', name: 'keywords', content: this.post.tags.join() })
}
if (this.post && this.post.slug) {
_head.meta.push({ hid: 'description', property: 'description', content: this.post.slug })
_head.meta.push({ hid: 'og:description', property: 'og:description', content: this.post.slug })
}
if (this.post && this.post.title) {
_head.meta.push({ hid: 'og:title', property: 'og:title', content: this.post.title })
}
return _head
}
}
以下為2篇不同頁面的結果
比較2張圖片右側開發者工具呈現的 meta 資訊就可以看出差異
主要是為了 seo 與 fb 分享可以針對文章的內容取得更精準的資訊
使用首頁來解析一下 Layout 切分方式
- 所有頁面都存在 Header 與 Footer,所以將這2個組件都放置於 Layout。
- 置入 <nuxt/> 讓切換路由時在此區塊渲染對應 pages 畫面。
layouts/default.vue 部份程式碼,可看到 template 中 HTML 排列方式
<template>
<div>
<page-header/>
<div class="container">
<nuxt/>
</div>
<page-footer/>
</div>
</template>
<script>
import PageHeader from '~/components/PageHeader'
import PageFooter from '~/components/PageFooter'
export default {
name: 'Pages',
components: {
PageHeader,
PageFooter
},
// ...略
}
</script>
客製錯誤頁面 layouts/error.vue
layouts/error.vue 部份程式碼
透過 props 傳遞 error 參數,取得相關的錯誤資訊提供畫面顯示使用
<template>
<div class="row">
<div class="col-12 text-center">
<h1 class="not-found-title">{{error.statusCode}}</h1>
<p class="text-black-50" v-if="error.statusCode === 404">Page not found</p>
<p class="text-black-50" v-else>Oops, the page you're looking for doesn't exist.</p>
<nuxt-link
to="/"
class="btn btn-dark"
>回首頁</nuxt-link>
</div>
</div>
</template>
<script>
export default {
name: 'Error',
props: ['error'],
// ...略
}
</script>
參考資料
請問在nuxt.config.js 中 輸入 path.join 會報錯該如何解決?