<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> ![](https://i.imgur.com/S3hgj23.png) # 建立部落格之 Nuxt3 篇 [Toc] --- ## 前言 ![](https://i.imgur.com/iCa34hQ.png =200x) 雖然主題是建立部落格,但是這次報告會有點偏離,會以了解 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 介紹 ![](https://i.imgur.com/fpO8HW8.gif =200x) 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) 流程圖 ![](https://i.imgur.com/juT76KU.png =800x) --- ## **建置專案** ``` 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 專案後瀏覽器呈現畫面 ![](https://i.imgur.com/1I8SSkl.png =400x260) --- ## **新增目錄** Nuxt3 在目錄上有自動匯入與約定功能,所以新增目錄時要遵守官方規範,請到 [官方目錄](https://v3.nuxtjs.org/guide/directory-structure/nuxt) 查看目錄設定,以免會無法正常顯示。 ![](https://i.imgur.com/cl6JSRV.png =400x) ---- ### 這些是我在專案新增的目錄,針對這些目錄分別來介紹。 ``` 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 創建路由。 ![](https://i.imgur.com/kioV0JD.png =400x) ---- #### **使用** ``` 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 提供三種方式可以處理特定頁面與所有頁面,目前專案未在目錄新增檔案,而是在特定頁面上新增路由守衛。 ![](https://i.imgur.com/ouRGSht.png =800x) ---- #### **三種方式** * 匿名-頁面中直接撰寫 (前一頁圖片範例) * 具名-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` ,還可以設定其他佈局,目前專案上有前後台,在佈局有些不同,可以透過新增目錄與檔案,在頁面裡設定覆蓋預設佈局即可,那就來實際操作一次。 ![](https://i.imgur.com/vl5dq8z.png =400x) ---- #### **使用-預設佈局** ``` 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 延遲載入與客戶端顯示等功能。 ![](https://i.imgur.com/rkhsq84.png =200x) <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/ 目錄使用方法一樣。 ![](https://i.imgur.com/XgdWDZ7.png =800x) ---- #### **進階設定** 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 數據。 ![](https://i.imgur.com/e38dp1N.png =400x) ---- #### **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.模組載入時間比較早,接下來我會依序介紹。 # ![](https://i.imgur.com/CWMny3m.png =400x) ---- #### **使用** 目錄只有掃描頂層與資料夾裡的 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 取得值,它只會在伺服器渲染後在客戶端交互期間存值,這樣可以避免警告產生。 ![](https://i.imgur.com/HLexQgA.png =400x) ---- #### **使用** ``` 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> ``` --- ## 範例 ![](https://i.imgur.com/OE1NJST.jpg =800x) 報告終於結束了~應該對於 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}]"}
    2110 views