<style type="text/css">
h1{
font-size: 30px!important;
}
h2{
font-size: 28px!important;
}
h3{
font-size: 24px!important;
}
h4{
font-size: 20px!important;
}
h5{
font-size: 18px!important;
}
li,p{
font-size: 20px!important;
}
.reveal p{
text-align: left!important;
}
.reveal a,.reveal a:hover{
color: green;
text-decoration: underline!important;
}
.reveal table{
width: 800px!important;
font-size: 18px!important;
}
img{
margin: 0 auto!important;
display: block!important;
}
</style>

# 建立部落格之 Nuxt3 篇
[Toc]
---
## 前言

雖然主題是建立部落格,但是這次報告會有點偏離,會以了解 Nuxt3 為主(我猶豫了很久到底要不要介紹),著重在 Nuxt 開發專案初期建置與方法,大家比較能進入狀況,在報告後續稍微看一下範例,還有需多 bug 日後會努力修正,講到這我要開始今天 Nuxt3 篇。
---
## 為什麼使用 Nuxt ?
在練習完 [Expree 部落格網站](https://work-node-blog.fly.dev/dashboard) 後,我的私心想要透過 Vue.js 來開發部落格。
為什麼最後沒有使用 Vue 開發 ? Vue 是單頁式應用 (SPA),透過 AJAX 技術更新網站內容,優點是網站流暢度與使用者體驗相比傳統網站來的好,缺點是搜尋引擎最佳化(SEO)表現不是很理想,因為在開啟檢視網頁原始碼的時候,往往只有 div 容器沒有內容,所以爬蟲到看不到任何內容,就會導致 SEO 分數低落,而建立部落格網站,希望能搜尋能在前幾排看到自己網站,以目前情況下,需要去解決這問題,如何解決? 這時就要講到 Nuxt 全端框架,它主要解決 Vue 關於 SEO 的缺點,使用伺服器端渲染 (SSR) 模式,除此之外它還有許多好用功能與設定愛不釋手,最後我採用 Nuxt 作為專案框架。
[SPA CSR MPA SSR是什麼](https://medium.com/web-design-zone/%E6%90%9E%E6%87%82%E5%89%8D%E7%AB%AF%E6%8A%80%E8%A1%93%E5%90%8D%E8%A9%9E-ssr%E8%88%87spa-450563784c47)
---
## Nuxt3 介紹

Nuxt 框架是基底 Vue,是 Vue 與相關套件、工具(Vue-Router、webpack、Vite ...)整合的框架,讓你開發時變得簡單又強大。
Nuxt 在 2022/11/16 清晨發布 Nuxt 3.0 穩定版,並且更新官網文件,內容比 RC 時更完整好觀看 ~~太好了我心情如上圖~~。
---
## Nuxt3 特色
* 自動匯入: 無需匯入 components、composables、Vue APIs,直接在應用程式中使用。
* 多種渲染模式: Client-side Only Rendering (SPA)/ 預設- Universal Rendering (SSR+SPA) / Hybrid Rendering (各路由禁用 SSR) / Rendering on CDN Edge Workers (ESR)
* 服務器引擎: Nitro 服務器引擎,無需任何配置下,直接在 server/ 目錄新增 api 、 middleware 、 routes,就可以在伺服器端新增邏輯、路由。
* 模組: 提供模組系統,簡化安裝擴充套件的程序。
<font size="2" color="gray">還有更多特色,但目前我還未體驗到,如果有興趣可以到官網去了解</font>
----
## Nuxt3 渲染
**通用渲染 Universal Rendering(預設)**
預設渲染模式,網站接收請求後,透過伺服器解析 vue 渲染 HTML 回傳給瀏覽器產生靜態頁,同時間瀏覽器下載 js (Vue) 程式碼加入交互特性(Hydration),達到解決 Vue SEO 與改善網頁流暢度與體驗(SPA+CSR)。
<font size="3" color="gray">瀏覽器產生靜態頁面後具有交互性時稱為 Hydration</font>[快速理解圖示](https://nuxt.com/docs/guide/concepts/rendering#universal-rendering)
流程圖

---
## **建置專案**
``` shell=
1.安裝 nuxt 專案
npx nuxi init <nuxt-blog>
2.專案開啟 vscode 編輯
code <nuxt-blog>
3.安裝套件管理工具
pnpm install -shamefully-hoist
4.開起專案+瀏覽器
pnpm run dev -- -o
```
----
### **補充**
* npx 是什麼? 安裝套件會安裝在臨時安裝包上,在執行完後就會刪掉;適合使用在?一次性套件與測試套件可以使用
* 官方說到使用 `pnpm install` 套件管理工具,之前需要有 `.npmrc` 與 `shamefully-hoist=true`,如果沒有會在開啟專案時看不到頁面。 [官方](https://v3.nuxtjs.org/getting-started/installation)
----
### `pnpm run dev -- -o` 開啟 Nuxt 3.0 專案後瀏覽器呈現畫面

---
## **新增目錄**
Nuxt3 在目錄上有自動匯入與約定功能,所以新增目錄時要遵守官方規範,請到 [官方目錄](https://v3.nuxtjs.org/guide/directory-structure/nuxt) 查看目錄設定,以免會無法正常顯示。

----
### 這些是我在專案新增的目錄,針對這些目錄分別來介紹。
```
nuxt-blog/
├── .nuxt (nuxt目錄-開發時依造目錄生成 vue 應用程序)
├── .output (輸出目錄)
├── assets/
├── components/ (元件目錄)
├── composables/ (共用邏輯目錄)
├── layouts/ (佈局目錄)
├── middleware/ (中介軟體目錄)
├── node_modules/
├── pages/ (頁面目錄-路由)
├── plugins/ (插件目錄)
├── public/
└── server/ (伺服器目錄)
└── api/
├── .gitignore
├── app.vue
├── nuxt.config.js
├── package.json
└── tsconfig.json
```
<font size="2" color="gray">如果需要了解所有目錄,請看官方文件</font>
---
### **1.頁面路由 pages/**
新增 pages/ 目錄, Nuxt3 會在背後執行 Vue Router 創建路由。

----
#### **使用**
```
1. 使用路由
pages/
├── index.vue (首頁)
2. 動態路由
pages/
└── users
└── [id].vue
3. 巢狀路由
pages/
└── parent/
└── child.vue
├── parent.vue (
4. 404 頁面
pages/
├── 404.vue
```
<font size="3" color="gray">* 還有自定義路由這部分跟寫 Vue Router 寫法相似,有興趣可以看文件</font> [了解更多](https://v3.nuxtjs.org/guide/directory-structure/pages#custom-routes)
<font size="3" color="gray">* `pnpm run build` 來建構出 .output 目錄,並打開.output/server/chunks/app/server.mjs 可以看到 router 設定</font>
----
#### **設定**
`<NuxtPage />`與 Vue Router 提供的 `<router-view />` 一樣,新增路由需要的進入點,通常是設定在頂層與巢狀路由上。
##### **修改頂層**
1. 改掉 `app.vue`裡的 `<NuxtWelcome />` 改成 `<NuxtPage />`,讓頁面之間可以切換路由。
``` vue=
// app.vue
<template>
<div>
<NuxtPage />
</div>
</template>
```
2. 新增首頁 `pages/index.vue`
---
### **2.中間件軟體 middleware/**
<font size="3" color="gray">自動匯入到應用程式可以直接使用</font>
新增 middleware/ 目錄,如同 Vue Router 提供路由守衛的 Hook API,Nuxt3 在 middleware 提供三種方式可以處理特定頁面與所有頁面,目前專案未在目錄新增檔案,而是在特定頁面上新增路由守衛。

----
#### **三種方式**
* 匿名-頁面中直接撰寫 (前一頁圖片範例)
* 具名-middleware 目錄中新增檔案,頁面中載入名稱。
* 全域-middleware 目錄中新增檔案,檔名加上 `.global` ex. auth.global.js ,全部路由都會經過這裡。
----
#### **使用**
Nuxt3 與 Vue router 的路由守衛有些不同,Nuxt3 Middleware 的參數中沒有 `next`,只要沒有轉址與取消路由,就會直接完成前往頁面,接下來透過以下來說明:
* `to`-前往路由
* `from`-原本的路由
* `return navigateTo()`-轉址
* `return abortNavigation()`-取消路由
``` javascript=
// middleware/auth.js
export default defineNuxtRouteMiddleware((to, from) => {
if (!isAuthlogin) {
return navigateTo('/dashboard/login')
}
});
```
---
### **3.佈局 layouts/**
<font size="3" color="gray">自動匯入到應用程式可以直接使用</font>
新增 layouts/ 目錄,將相同區塊( header footer)直接套用每個頁面上;新增默認佈局 `default.vue` ,還可以設定其他佈局,目前專案上有前後台,在佈局有些不同,可以透過新增目錄與檔案,在頁面裡設定覆蓋預設佈局即可,那就來實際操作一次。

----
#### **使用-預設佈局**
``` vue=
// layouts/default.vue
<template>
<div>
<header>...</header>
<slot />
<footer>...</footer>
</div>
</template>
```
```
// app.vue
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
```
----
#### **使用-新佈局**
``` vue=
// 覆蓋默認佈局 app.vue
<template>
<NuxtLayout name="dashboard">
<NuxtPage />
</NuxtLayout>
</template>
// 頁面上更換佈局 pages/dashboard/index.vue
<template>
<div>content</div>
</template>
<script setup>
definePageMeta({
layout: "dashboard",
});
</script>
```
[更多方法](https://v3.nuxtjs.org/guide/directory-structure/layouts#layouts-directory)
---
### **4.新增元件 components/**
新增 components/ 目錄,那為什麼特別講呢~因為在 Vue 專案開發時要使用元件需要載入與註冊(撇除自動載入套件),但是 Nuxt 3 會自動匯入,除此之外元件上使用還有 lazy 延遲載入與客戶端顯示等功能。

<font size="3" color="gray">* 延遲載入與客戶端顯示,目前專案中我還未使用,日後可能需要,請自行看文件</font>
----
#### **使用**
路徑目錄與檔案命名會結合後自動載入,可以直接在 `template` 使用,但要留意目錄與檔案命名。(我在這卡了超久)
```
/ components
├── form/ (公共目錄)
└── combobox.vue
```
``` vue=
// 頁面中直接使用元件
<template>
<div>
<form-combobox/> or
<FormCombobox/>
</div>
</template>
```
[了解更多](https://v3.nuxtjs.org/guide/directory-structure/components)
---
### **5.組合式函數 composables/**
<font size="3" color="gray">自動匯入到應用程式可以直接使用</font>
新增 composables/ 目錄,介紹前提一下 Vue Composition API 的封裝功能讓你在不同元件裡使用相同邏輯,這個目錄也是把重複使用邏輯封裝成檔案,可以在應用程式中直接使用; utils/ 目錄使用方法一樣。

----
#### **進階設定**
composables 目錄只能掃描頂層文件,無法新增資料夾,如果想要擁有多層目錄,可以在 `nuxt.config.js` 設定:
```javascript=
export default defineNuxtConfig({
imports: {
dirs: [
// 掃描頂層
'composables',
// 掃描下層目錄的 index 檔
'composables/*/index.{ts,js,mjs,mts}',
// 掃描所有目錄的檔案
'composables/**'
]
}
})
```
----
#### **使用**
```javascript=
// composables/useCount.js
// 1. 具名
export const useCount = () => useState('count',()=>0);
// 2. 預設 (使用檔名)
export default function () {
return useState('count',()=>0)
}
```
```vue=
<template>
<div>
{{ count }}
</div>
<button @click="count+=1"></button>
</template>
<script setup>
const count = useCount()
</script>
```
<font size="3" color="gray">* useState 後續會提到很重要的方法</font>
---
### **6.伺服器 Server**
新增 server/ 目錄,自動掃描目錄中 `/server/api` 、`/server/routes` 、`/server/middleware` 資料夾的檔案,註冊 API 與路由。
* 每個檔案你面都要定義默認函數`defineEventHandler()`。
* 回傳值直接 return JSON 數據。

----
#### **API 新增**
1. 新增 `/server/api/` 資料夾
2. 新增 `hello.get.js` 檔案,後綴.get, .post, .put, .delete, ... 以匹配請求的 HTTP Method
``` javascript=
export default defineEventHandler((event) =>
return 'Hello Mandy!'
})
```
3. 使用 `useFatch` 取得資料(後續會講到此方法)
```vue=
<template>
<div>{{ data }}</div>
</template>
<script setup>
const { data } = await useFetch('/api/hello')
</script>
```
----
#### **Routes 路由**
1. 新增 `/server/routes/` 資料夾
2. 新增 `hello.js` 檔案
``` javascript=
export default defineEventHandler((event) => 'Hello Mandy!')
```
3. 產生 `/hello` 頁面路由
----
#### **middleware 中間件**
針對伺服器的每個路由前執行,適合檢查標頭、記錄請求或擴展事件的請求對象。
1. 新增 `/server/middleware/` 資料夾
2. 新增 `auth.js` 檔案
```javascript=
export default defineEventHandler((event) =>
event.context.auth = { user: 123 }
})
```
---
### **7.插件 plugins**
<font size="3" color="gray">自動匯入到應用程式可以直接使用</font>
新增 plugins/ 目錄,擴充功能(套件)。
Nuxt3 對於新增擴充功能有兩種做法,1.模組系統 2.插件,對於這兩項使用差別在於 1.有支援模組,在安裝會節省許多重複設定,相對的比插件設定來的簡單。2.模組載入時間比較早,接下來我會依序介紹。
# 
----
#### **使用**
目錄只有掃描頂層與資料夾裡的 index 檔,會被註冊為插件
```
plugins
| - plugin.ts
| - otherPlugin
| --- index.js
```
nuxtApp 包含了各種的實例 ex: 安裝 vue 插件 `nuxtApp.vueApp.use`
``` javascript=
export default defineNuxtPlugin(nuxtApp =>
console.log(nuxtApp)
})
```
----
Automatically Providing Helpers
提供 helper,可以在插件 return 物件回傳 provide
```javascript=
export default defineNuxtPlugin(() => {
return {
provide: {
hello: (name) => `Hello ${name}`
}
}
})
```
```vue=
<template>
<div>
{{ $hello('Mandy') }}
</div>
</template>
<script setup lang="ts">
const { $hello } = useNuxtApp()
</script>
```
----
### **模組 Modules**
[查詢模組](https://nuxt.com/modules)
**使用**
```shell=
pnpm install @nuxtjs/example
```
```javascript=
// nuxt.config.[ts/js]
export default defineNuxtConfig({
modules:['@nuxtjs/example']
})
```
---
## 資料取得 Data Fetching
Nuxt 提供 useFetch、 useLazyFetch 取得資料的方法,不需在使用 Axios 與 Fetch API 來取得。
| API | 說明 |
| ---- | ---- |
| useFetch | 資料取得; 在參數新增 lazy 參數也可以達到 useLazyFetch 效果。 |
| useLazyFetch | 請求資料時,不會阻塞讓頁面繼續渲染 |
<font size="3" color="gray">useFetch 和 useLazyFetch 以外還有兩種 useAsyncData 和 useLazyAsyncData ,差別在 useFetch 是 useAsyncData 進階版。~~那我就不介紹了~~ </font>
----
#### **參數 Params** [繼承自 useAsyncData 的選項請看](https://nuxt.com/docs/api/composables/use-fetch#params)
```
const { Return Values } = await useFetch(URL,{ Params })
```
| 參數 | 說明 |
| ---- | ---- |
| method | 常發送 HTTP 請求的方法,例如 `GET`、`POST` 或 `DELETE` 等。 |
| params | 查詢參數 (Query params) |
| body | 請求的 body,可以傳入一個物件,它將自動被轉化為字串 |
| headers | 請求的標頭 (headers) |
| baseURL | 請求的 API 路徑,基於的 URL |
----
#### **回傳值 Return Values**
```
const { Return Values } = await useFetch(URL,{ Params })
```
| 回傳值 | 說明 |
| ---- | ---- |
| data | 回傳結果 |
| params | 是否正在獲取資料 |
| refresh / execute | 一個函數,可以用來重新執行 `handler` 函數,回傳新的資料,類似重新整理、重打一次 API 的概念。預設情況下 `refresh()` 執行完並回傳後才能再次執行。 |
| error | 資料獲取失敗時回傳 |
----
#### **使用**
`useFetch`
```vue=
// useFetch
<script setup>
const { data: count } = await useFetch('/api/count')
</script>
```
`useLazyFetch`
```vue=
<template>
<div v-if="pending">
Loading ...
</div>
<div v-else>
<div v-for="post in posts">{{post}}</div>
</div>
</template>
<script setup>
const { data: posts,pending } = useLazyFetch('/server-routes', {
})
</script>
```
---
## 資料取得 State Management
Nuxt 提供 useState,是響應式物件與 SSR 友善的共享狀態,也是 SSR 友善的 ref 替代品。
<font size="3" color="gray">共享狀態: 類似 Pinia State,設定 `useState('count',()=> 0)`,在各個元件中都可以透過 `useState('count')` 取得</font>
為什麼是 ref 替代品?因為在 Nuxt Universal Rendering 渲染,伺服器端解析 vue 渲染 ref 的值會被存起來,同時在客戶端下載 js (Vue) 程式產生頁面互動性,下載同時客戶端會重新更動 ref 的值,如果更動的值不一樣,這時候會出現 Vue Warn Hydration Client 與 Server 值不同情況(下圖)。
如何解決不同值問題?使用 useState 取得值,它只會在伺服器渲染後在客戶端交互期間存值,這樣可以避免警告產生。

----
#### **使用**
``` vue=
<script setup>
const count = useState('count',()=> 0)
or
const count = useState(()=> 0)
</script>
```
---
## SEO 與 Meta
Meta Tag 稱為描述標籤,出現於 Html 語法`<head>`裡,這區塊的 Meta 不會顯示在頁面上,主要是讓搜尋引擎與社群網站知道這網頁標題、內容描述、重要關鍵字等,是網站 SEO 重要一環,設置的好讓網站搜尋排名與點擊率提升。
<font size="3" color="gray">接下來只會講到要如何新增 head 裡的標籤,不會說到新增哪些 Meta tag,如果想要了解我提供了兩個網址</font> [可以新增哪些](https://www.oxxostudio.tw/articles/201406/social-meta.html) | [標籤需要留意](https://welly.tw/serp-rank-optimization/what-is-meta-title-and-description)
Nuxt 有提供三種新增 head 裡的標籤方法,分別在設定檔設定 head 屬性、各頁面設定 `useHead` 、頁面新增提供`<head>`與各種標籤元件。
---
#### **1.設定檔設定**
這設定全站會都會使用,需留意這裡的設定不提供響應式,如果想設定可以在 app.vue 裡設定 `useHead`
```javascript=
// nuxt.config.js
export default defineNuxtConfig({
app: {
head: {
title: 'Mandy 部落格',
meta: [
{ name: 'description', content: '這是一個 Nuxt3 的練習 Blog' }
],
}
}
```
---
#### **2.各頁面設定**
除了可以在 app.vue 設定讓全站設定,各頁也能夠分別設定,有一樣的值的話,會以當前頁為主覆蓋掉 app.vue 設定。
``` javascript
// app.vue
<script setup>
useHead({
link: [
{
rel: 'shortcut icon',
href: 'images/icon/logo.ico'
}
],
meta: [
{
name: 'description',
content: '這是一個 Nuxt3 的練習 Blog'
},
],
});
</script>
```
----
#### **標題模板**
能設定在 app.vue 或佈局目錄裡面的檔案,能讓所有頁面使用這個模板
```vue=
// layouts/default.vue
useHead({
titleTemplate: title => {
return title ? `${title} - Mandy 部落格` : 'Mandy 部落格';
}
});
```
----
#### **3.新增標籤元件**
Nuxt 提供 `<Title>, <Base>, <NoScript>, <Style>, <Meta>, <Link>, <Body>, <Html> and <Head>` 元件。
<font size="3" color="gray">*元件名稱開頭大寫與原生 HTML 元素有匹配,所以請勿隨意修改。</font>
```vue=
<script setup>
const title = ref('Hello Mandy')
</script>
<template>
<div>
<Head>
<Title>{{ title }}</Title>
<Meta name="description" :content="title" />
</Head>
<h1>{{ title }}</h1>
</div>
</template>
```
---
## 範例

報告終於結束了~應該對於 Nuxt 初步環境與設定大置有概念,其實還有功能與設定還沒接觸,無法在這次報告講解,有興趣的話可以去查詢官方文件,那....我們就進入範例吧~
{"metaMigratedAt":"2023-06-17T13:10:20.971Z","metaMigratedFrom":"YAML","title":"建立部落格之Nuxt3篇","breaks":true,"slideOptions":"{\"theme\":\"solarized\",\"transition\":\"fade\"}","contributors":"[{\"id\":\"6fa4fc8a-d603-405a-8037-2346b90116a2\",\"add\":58213,\"del\":44234}]"}