Vue3 筆記 | Nuxt

閱讀時間約 24 分鐘

Nuxt 可以說是 Vue 這個框架中的框架,它的出現讓大型開發變得更加簡單。

基本上,Nuxt 的出生是為了解決 SPA 在 SEO 方面的不足,因此 Nuxt 主要是用來建立 SSR (Server-Side Rendering) 網頁。不過當然,它還是可以拿來建立 SPA 網頁,畢竟它整合的功能實在太過方便 ,光是路由的設定不用在像 vue-router 要寫一堆就很值得使用。



Nuxt 專案啟動

建立新 Nuxt 專案:

npx nuxi@latest init nuxt-app

進入到新專案路徑底下,記得載入需求套件:

npm install

接著就可以開啟網頁看看啦:

npm run dev
raw-image



Nuxt 環境簡單介紹

在 Nuxt 專案下會發現環境好像異常的空,比之前用 vite 建立專案少了一些資料夾,比如說 components 之類的。這不代表 Nuxt 沒有,但它選擇並不幫你預設建立,而是讓我們自己按照需求新增。

raw-image

比方說在官網的這裡就有說明我們可以建立哪些資料夾來管理檔案,並讓 Nuxt 幫我們自動管理。舉例來說:

  1. components:存放元件的地方。
  2. pages:存放頁面的地方。
  3. composables:把常用的功能寫成一個函數共用儲存的地方。
  4. assets:用來存放需要打包的靜態檔案,諸如各式各樣 CSS、SCSS 或字型。
  5. public:用來存放那些不需要打包的靜態檔案,比如 favicon。
  6. middleware:路由中間件放置處,比如跳轉頁面時要先運行的身分驗證。
raw-image



nuxt.config.ts

我原本打算把這個放最後記錄,但後來還是決定把它放前面先講,因為它似乎有些重要,一些好用的功能都必須在這個 Nuxt 設定檔先做設定,所以還是先介紹一下。

我們在一開始建立完 Nuxt 專案後,打開 nuxt.config.ts 會看到 Nuxt 預先做好了一個設定:

export default defineNuxtConfig({
devtools: { enabled: true }
})

這個是 "是否啟用開發者工具" 的意思,刪掉也不會怎樣。

在這個 Nuxt 設定檔中,通常會因為你需要什麼功能而來這裡進行一些配置,像本文後段的設定網站標頭就會提到要做哪些設定。

這裡僅介紹一個我認為滿好用的設定:runtimeConfig

先給大家看一眼官網的寫法:

// ​nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// 只在伺服器可見
apiSecret: '123',
// 伺服器或瀏覽器皆可見
public: {
apiBase: '/api'
}
}
})
// .env
// 可以覆蓋掉​apiSecret的內容
NUXT_API_SECRET=api_secret_token

這樣的設定好處是放在 private 的資訊都不會出現在客戶端的程式碼裡,大大提高了網頁安全性。

想像一下,一個應用程式需要使用一個 API,而該 API 需要 API 金鑰進行身份驗證。在這種情況下,你可以將 API 金鑰放在 private 中,達到伺服器端和客戶端之間共享配置信息,同時保護了一些敏感數據不被公開暴露在客戶端。



pages

單獨抓它出來講,是因為 pages 建立路由這功能太過於方便了,方便到說到 Nuxt 除了 SSR 就是建立路由很方便這件事。

現在我們在專案下建立一個 pages 資料夾,並在裡面建立一個 index.vue 檔案,稍微打些內容:

<template>
<h1>I am main page</h1>
</template>

然後回到 app.vue 把程式碼改成下列:

<template>
<div>
<NuxtPage />
</div>
</template>

我們就會看到畫面成功渲染出 "I am main page" 。

當我們在 pages 裡面建立頁面檔案時,Nuxt 就會幫我們建立好相對應的路由,用的原理其實是 vue-router 的原理,只是我們可以省去自己去寫 path 的那些時間。

而在 pages 中,通常命名為 index 的都會被預設為主頁,然後當我們在 app.vue 這個最終出口設定 <NuxtPage /> 指定渲染位置後,畫面就會跑出來。

現在來試試建立一個 about 頁面。同樣在 pages 下新增一個 about.vue 檔案,也給一些簡單的內容:

<template>
<h1>I aam about page</h1>
<NuxtLink to="/">Home</NuxtLink>
</template>

然後把 index.vue 稍微調整一下,加個去 about 頁面的連結:

<template>
<h1>I am main page</h1>
<!-- 添加我 -->
<NuxtLink to="/about">About</NuxtLink>
</template>

現在可以看到可以很順利在兩個頁面中做跳轉,Nuxt 很輕鬆地幫我們完成路由的設定。

動態路由

肯定會有人問:那 Nuxt 的動態路由怎麼搞?沒錯,那人就是我!

在 Nuxt 玩動態路由只要把檔名用 [ ] 包起來就好,我們可以嘗試在 pages 李建立一隻叫 [id].vue 的檔案,然後輸入下列 code:

<template>
<h1>page: {{ $route.params.id }}</h1>
</template>

然後我們手動更改我們的網址進入 http://localhost:3000/55、http://localhost:3000/62 之類的,後面的參數自己換,可以看到路由確實可以切換,並且也成功渲染出動態參數到頁面上。

巢狀路由

在 Nuxt 裡建立巢狀路由,方便的程度超乎你我想像。我們先在 pages 下建立一個 user.vue 和 user 資料夾,並在 user 資料夾中新增一個 [user].vue (對,這裡讓我結合一下動態路由玩玩),像下圖這樣:

raw-image

接著我們為 user.vue 加點東西:

<template>
<h1>default user page</h1>
<NuxtPage/>
</template>

也為 [user].vue 加點內容:

<template>
<h1>{{ $route.params.user }}</h1>
</template>

現在我們前往 http://localhost:3000/user 跟 http://localhost:3000/user/jeremy 就會看到畫面的改變:

可以發現路由的嵌套在 Nuxt 中變成只要手手點一下,創個資料夾、建立個檔案就完成的事。瞧瞧,我們只要在專案路徑下建立這樣的結構:

-| pages/
---| parent/
------| child.vue
---| parent.vue

就等同在 vue-router 中寫這樣的內容:

[
{
path: '/parent',
component: '~/pages/parent.vue',
name: 'parent',
children: [
{
path: 'child',
component: '~/pages/parent/child.vue',
name: 'parent-child'
}
]
}
]

而實際上,我們只是把手寫這些定義路由內容的部分交給 Nuxt 處理而已。但是!但是!請務必記得在父層加一個<NuxtPage/>,不然就會像我一樣找半天找不到為何子路由渲染不出來的問題 @@

多層級路由

在 Nuxt 中做多層級路由就是用資料夾一個套住一個,比方說我想建立一個 /plant/flower/lily 這樣的路由,並且在每一層的時候渲染的東西都不一樣,也就是說 /plant 有屬於他自己的頁面、/plant/flower 也有自己的頁面 (跟巢狀不一樣,不要搞混)。

實作上會建立出像下圖這樣的資料夾結構:

-| pages/
---| plant/
------| index.vue
------| flower/
-----------| index.vue
-----------| lily/
----------------| index.vue
raw-image

每一層級的資料夾都有一個 index.vue 去代表這個路由下該渲染的畫面,然後再包其他資料夾這樣一層疊一層。

路由匹配

因為前面的太方便了,所以路由驗證就要與眾不同一點 (x

好啦,反正各位都知道在 vue-router 中如果想要依動態路由匹配去渲染不同畫面是這樣做 (讓我抄一下我前面文章的 code):

{
path: '/about/:afterAbout',
component: AboutView
},
{
path: '/about/:afterAbout(\\d+)',
component: NotFound
}

但很明顯,在 Nuxt 中我們不可能在檔案名稱上寫正規表達式。所以這件事情就落到要去 <script setup> 中來使用 Nuxt 提供的方法。

現在來做一個路由結合多層路由的例子,我們先建出下列這個結構:

-| pages/
---| books/
------| index.vue
------| [id]/
-----------| index.vue

注意喔,有一個資料夾是命名為 [id],表示要套用動態路由。接著我們在 /books/index.vue 加一些內容:

<template>
<h1>books page</h1>
</template>

然後在 /books/[id]/index.vue (這個不是路由) 下加一些內容:

<script setup>
const route = useRoute()
</script>

<template>
<h1>{{ route.params.id }}</h1>
</template>

現在先嘗試換換看動態參數的部分看看網頁是不是如期運作。

raw-image

現在來實作驗證的部分,我們現在要動態 :id 為數字時才出現像剛剛的內容,如果不是數字就幫我導引回 /books 這個路由。

重新回到/books/[id]/index.vue,我們修改如下:

<script setup>
const route = useRoute()

// 驗證如下​
definePageMeta({
validate: async (route) => {
// 檢查id是否由數字組成
if(/^\d+$/.test(route.params.id)){
return true
}else{
return {path: `/books`}
}
}
})
</script>

<template>
<h1>{{ route.params.id }}</h1>
</template>

definePageMetavalidate 是 Nuxt 提供來做路由匹配的。它所返回的 boolean 值會決定是否渲染這個頁面,所以一定要記得 return true!不然就會像我一樣發生錯誤匹配進行很順利,但 id 為數字時一值出現 404 error。



layouts

layouts 單獨拿出來講也必有其獨特之處。通常來說,會用到 layout 的情況就是複數頁面有共用的組件或排版樣式,比如說導覽列。

layouts 資料夾中,命名為 default.vue 的會是預設為全局 layout,我們只要在 app.vue 中給予 <NuxtLayout> 設定,就完成了一半的 layout 引用:

<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>

然後我們必須在 layout (以 default.vue 為例) 中,在想要套入其他頁面或元件的地方加一個 <slot/>

<template>
<div>
<slot />
</div>
</template>

而如果今天有某特定頁面想引用一個特定的 layout,比如說登入、註冊頁面想套用同一個 layout (比如 sign-in-up.vue) 而不是 default.vue,我們可以在 <script setup> 中進行頁面設定:

<script setup lang="ts">
definePageMeta({
layout: 'sign-in-up'
})
</script>



Nuxt 中設定網站標頭

在 Nuxt 專案裡面,我們要設定網站標頭,也就是原先寫在 HTML head 的那些東西必須透過兩道程序做設定。

我們必須先到 nuxt.config.ts 中設定下列內容:

export default defineNuxtConfig({
// 不要管我,我是開發者工具
devtools: { enabled: true },
// 添加我!!!
app: {
head: {
charset: 'utf-8',
viewport: 'width=device-width, initial-scale=1',
}
}
})

然後到我們一切路由與元件的出入口 app.vue,添加下面這這一段:

<script setup lang="ts">
useHead({
title: '這裡是title',
meta: [
{ name: 'description', content: '這是測試用網站' }
],
bodyAttrs: {
class: 'test'
},
script: [ { innerHTML: 'console.log(`Hello world`)' } ]
})
</script>

這樣可以動態生成 HTML 元素,以配置網頁的標題、描述、body 屬性和一段內嵌的 JavaScript 代碼。這樣的設置通常用於簡化網頁開發過程,讓開發者能夠更輕鬆地管理和設定網頁的相關資訊。



轉場效果

Vue 和 vue-router 都有的轉場效果沒道理 Nuxt 沒有,只是要使用就必須先在 nuxt.config.ts 中先做一些設定:

export default defineNuxtConfig({
app: {
pageTransition: { name: 'page', mode: 'out-in' }
}
})

然後到 app.vue 添加樣式:

<style>
.page-enter-active,
.page-leave-active {
transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
filter: blur(1rem);
}
</style>

這時就可以觀察到在頁面切換時會有轉場效果了。

如果後臺噴出這個錯誤:"... does not have a single root node and will cause errors when navigating between routes.",請把各頁面的 <template> 中再加入一層 <div>,像這樣:

<template>
<div>
<h1>I am main page</h1>
<NuxtLink to="/about">About</NuxtLink>
</div>
</template>

我們也可以僅針對一個頁面做轉場,比如進到 about 頁面才會有轉場效果,這時必須先在 /page/about.vue 中做設定:

<script setup lang="ts">
definePageMeta({
pageTransition: {
name: 'rotate'
}
})
</script>

然後回 app.vue 添加樣式,記得使用剛剛定義的 name:

<style>
// 添加下列
.rotate-enter-active,
.rotate-leave-active {
transition: all 0.4s;
}
.rotate-enter-from,
.rotate-leave-to {
opacity: 0;
transform: rotate3d(1, 1, 1, 15deg);
}
</style>

這樣就可以讓 about 頁面有自己的轉場效果了。

不過其實 Nuxt 官網提供了許多轉場的作法,可以視使用情況去看一下該做哪些設定。



數據獲取

Nuxt 提供了三個方便獲得數據的方法:useFetchuseAsyncData$fetch,都是使用 Nuxt 打 API 的好夥伴 (默默放下 axios...)。

但在這裡我只想記錄 useFetch,為什麼呢?

仔細看官網解說,會發現官網說這其實就是useAsyncData$fetch的結合 ​:

Indeed, useFetch(url) is nearly equivalent to useAsyncData(url, () => $fetch(url)) - it's developer experience sugar for the most common use case.

官網自己都說了,useFectch 在絕大多數情況下就是這兩者的語法糖,所以就大膽用吧!(遇到例外狀況再去看說明書 www)

使用上也非常簡單:

<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>



狀態管理

useState

Nuxt 提供了 useState 來管理狀態,這個名詞如果是從 React 來的應該都不陌生 XD

先來看官網例子:

<script setup lang="ts">
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>

<template>
<div>
Counter: {{ counter }}
<button @click="counter++">
+
</button>
<button @click="counter--">
-
</button>
</div>
</template>

可以看到 useState 定義的 counter 就是用來儲存狀態的地方,後面的 arrow function 只是用來定義他的初始值。

Pinia

useState 輕輕帶過就是為了這個更重要、更好用的 Pinia!對,Pinia 又出現啦!

在 Nuxt 中使用 Pinia 請先按下列順序做設定:

  1. 安裝 Pinia
npm install pinia @pinia/nuxt
  1. 去 nuxt.config.js 進行設定:
export default defineNuxtConfig({
modules: [
[
'@pinia/nuxt',
{
autoImports: [
// 自動引入 `defineStore()`
'defineStore',
// 自動引入 `defineStore()` 并重新命名為 `definePiniaStore()`
['defineStore', 'definePiniaStore'],
],
},
],
],
})
  1. 建立一個 stores 資料夾,並在裡面建立一個 index.ts:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}

return { count, doubleCount, increment }
})
  1. 來引入 store (index.ts) 做使用,比如在 index.vue 中使用:
<script setup>
import {useCounterStore} from '~/stores/index'

const store = useCounterStore()
</script>

<template>
<h1>{{ store.count }}</h1>
<button @click="store.increment">Add</button>
</template>



參考資料

  1. Nuxt3 官方文件 (中文版 / 英文版)
18會員
37內容數
這個專題用來存放我在學習網頁開發時的心得及知識。
留言0
查看全部
發表第一個留言支持創作者!
Jeremy Ho的沙龍 的其他內容
Vue3 筆記,指令進階篇
Vue3 學習筆記,vue-router 篇。
Vue3 學習筆記,用 Pinia 管理資料與邏輯
Vue3 筆記,指令進階篇
Vue3 學習筆記,vue-router 篇。
Vue3 學習筆記,用 Pinia 管理資料與邏輯
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
Thumbnail
前言 Vue 是一個現代開發框架,擁有完尚的生態系,讓我們可以將須多元件客製化,做出組件,並且可重複利用,高擴充性。在開發組件時,每個組件都擁有自己的生命周期,Vue 組件會偵測每個變數值,是否有變,並且更新內容,今天要一個一個了解 Vue 的生命週期,讓大家有更多認識。 Vue 的生命週期
父元件 傳遞方法使用@ <template>    ...    <Login @modalClose="modalClose"/> ... </template> <script setup>     const _modal = ref();     function m
父元件 傳遞變數時須加上冒號 子元件 接收props用法如下 本筆記參考: 1. https://www.netlify.com/blog/understanding-defineprops-and-defineemits-in-vue-3.2 2. https://juejin.cn/post/7
Thumbnail
雖然距離上次Vue直播班課不到一年,但看到這次的課程有Pinia內容,手又不小心刷了魔法小卡(? 意識到自己的成長應該是可以輕鬆地串接API,畢竟去年也已經串到可以去烤串店了XD,就算是重新複習,還是可以從中獲得新的成長,而且發現到老師一年講的比一年還好,今年有很多觀念講得更清楚了! 可惜這次第六週
Thumbnail
Create project 裝好 vue CLI 之後,只要在終端輸入 vue create 想取的專案名稱 就可以開始創建 vue 專案囉 1. Pick a preset 2. Select the features 3. Choose the version of vue 選擇 vue3
Thumbnail
續上篇,那是Vue 2 Options API的舊寫法,這邊改成用Vue 3 Composition API的寫法: Vue2: Vue3 setup() / ref / reactive Composition API可以直接在setup()裡面定義data跟method,簡潔許多。 props
Thumbnail
Nuxt.js 是以 Vue 為基底所建構的框架,透過 Nuxt.js,我們能夠更輕鬆地開發靜態頁面 (Static Site)、操作體驗良好的單頁式網站 (SPA)、甚至是顧及 SEO 的伺服器端渲染 (SSR) 網站。
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
Thumbnail
前言 Vue 是一個現代開發框架,擁有完尚的生態系,讓我們可以將須多元件客製化,做出組件,並且可重複利用,高擴充性。在開發組件時,每個組件都擁有自己的生命周期,Vue 組件會偵測每個變數值,是否有變,並且更新內容,今天要一個一個了解 Vue 的生命週期,讓大家有更多認識。 Vue 的生命週期
父元件 傳遞方法使用@ <template>    ...    <Login @modalClose="modalClose"/> ... </template> <script setup>     const _modal = ref();     function m
父元件 傳遞變數時須加上冒號 子元件 接收props用法如下 本筆記參考: 1. https://www.netlify.com/blog/understanding-defineprops-and-defineemits-in-vue-3.2 2. https://juejin.cn/post/7
Thumbnail
雖然距離上次Vue直播班課不到一年,但看到這次的課程有Pinia內容,手又不小心刷了魔法小卡(? 意識到自己的成長應該是可以輕鬆地串接API,畢竟去年也已經串到可以去烤串店了XD,就算是重新複習,還是可以從中獲得新的成長,而且發現到老師一年講的比一年還好,今年有很多觀念講得更清楚了! 可惜這次第六週
Thumbnail
Create project 裝好 vue CLI 之後,只要在終端輸入 vue create 想取的專案名稱 就可以開始創建 vue 專案囉 1. Pick a preset 2. Select the features 3. Choose the version of vue 選擇 vue3
Thumbnail
續上篇,那是Vue 2 Options API的舊寫法,這邊改成用Vue 3 Composition API的寫法: Vue2: Vue3 setup() / ref / reactive Composition API可以直接在setup()裡面定義data跟method,簡潔許多。 props
Thumbnail
Nuxt.js 是以 Vue 為基底所建構的框架,透過 Nuxt.js,我們能夠更輕鬆地開發靜態頁面 (Static Site)、操作體驗良好的單頁式網站 (SPA)、甚至是顧及 SEO 的伺服器端渲染 (SSR) 網站。