vocus logo

方格子 vocus

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 官方文件 (中文版 / 英文版)
留言
avatar-img
Jeremy Ho的沙龍
20會員
37內容數
這個專題用來存放我在學習網頁開發時的心得及知識。
Jeremy Ho的沙龍的其他內容
2023/12/04
Basic python data structure
Thumbnail
2023/12/04
Basic python data structure
Thumbnail
2023/12/03
從 leetcode 學資料結構堆疊 (stack)
Thumbnail
2023/12/03
從 leetcode 學資料結構堆疊 (stack)
Thumbnail
2023/11/25
從 JavaScript 到 Python
Thumbnail
2023/11/25
從 JavaScript 到 Python
Thumbnail
看更多
你可能也想看
Thumbnail
創作不只是個人戰,在 vocus ,也可以是一場集體冒險、組隊升級。最具代表性的創作者社群「vocus 野格團」,現在有了更強大的新夥伴加入!除了大家熟悉的「官方主題沙龍」,這次我們徵召了 8 位領域各異的「個人主題專家」,將再度嘗試創作的各種可能,和格友們激發出更多未知的火花。
Thumbnail
創作不只是個人戰,在 vocus ,也可以是一場集體冒險、組隊升級。最具代表性的創作者社群「vocus 野格團」,現在有了更強大的新夥伴加入!除了大家熟悉的「官方主題沙龍」,這次我們徵召了 8 位領域各異的「個人主題專家」,將再度嘗試創作的各種可能,和格友們激發出更多未知的火花。
Thumbnail
vocus 最具指標性的創作者社群──「野格團」, 2026 年春季,這支充滿專業、熱情的團隊再次擴編,迎來了 8 位實力堅強的「個人主題專家」新成員 💫💫💫 從投資理財、自我成長、閱讀書評到電影戲劇,他們各自帶著獨特的「創作超能力」準備在格友大廳與大家見面。
Thumbnail
vocus 最具指標性的創作者社群──「野格團」, 2026 年春季,這支充滿專業、熱情的團隊再次擴編,迎來了 8 位實力堅強的「個人主題專家」新成員 💫💫💫 從投資理財、自我成長、閱讀書評到電影戲劇,他們各自帶著獨特的「創作超能力」準備在格友大廳與大家見面。
Thumbnail
對,沒錯。Nuxt 是一個框架 (Vue) 的框架。
Thumbnail
對,沒錯。Nuxt 是一個框架 (Vue) 的框架。
Thumbnail
Vue 筆記,Nuxt 簡介
Thumbnail
Vue 筆記,Nuxt 簡介
Thumbnail
Vue3 學習筆記,vue-router 篇。
Thumbnail
Vue3 學習筆記,vue-router 篇。
Thumbnail
Vue3 學習筆記,專案建立與基礎響應式篇
Thumbnail
Vue3 學習筆記,專案建立與基礎響應式篇
Thumbnail
安裝Node.js 使用nuxi建立nuxt 3 專案: 3. 進入專案目錄後,安裝相關套件: 4. 啟動nuxt: 跟Nuxt 2用法一樣,新增一個元件放到components資料夾中,可以直接引用: pages用法也一樣,這邊把app.vue改放到pages下,改成index.vue,然後新增一
Thumbnail
安裝Node.js 使用nuxi建立nuxt 3 專案: 3. 進入專案目錄後,安裝相關套件: 4. 啟動nuxt: 跟Nuxt 2用法一樣,新增一個元件放到components資料夾中,可以直接引用: pages用法也一樣,這邊把app.vue改放到pages下,改成index.vue,然後新增一
Thumbnail
1. 安裝node.js 2. 建立專案: 接著會詢問一些初始化設定的東西,按照需求選擇即可: 3. 執行 打開http://localhost:3000/ 即可看到畫面。 pages Nuxt.js會自動配置pages中每一個.vue檔案的route。 假設在pages資料夾下新增一個如下內容的a
Thumbnail
1. 安裝node.js 2. 建立專案: 接著會詢問一些初始化設定的東西,按照需求選擇即可: 3. 執行 打開http://localhost:3000/ 即可看到畫面。 pages Nuxt.js會自動配置pages中每一個.vue檔案的route。 假設在pages資料夾下新增一個如下內容的a
Thumbnail
專案建好了,那先來講 Vue 的專案架構 詳細內容很多,所以我挑重點講 public index.html public/index.html 是 Vue 頁面的 entry point,進入一個 Vue 頁面會先進 public/index.html,再套用 App.vue,最後才是進入你寫的 .
Thumbnail
專案建好了,那先來講 Vue 的專案架構 詳細內容很多,所以我挑重點講 public index.html public/index.html 是 Vue 頁面的 entry point,進入一個 Vue 頁面會先進 public/index.html,再套用 App.vue,最後才是進入你寫的 .
Thumbnail
前言 Vue 在過去的一兩年內快速發展,寫法也一直改變,我在過去一年多也更改了兩次寫法,在這個系列中我將以最新的 composition api + typescript 寫法進行教學。 javascript 有兩大痛點,一個是異步問題,另一個是弱型別。要渲染畫面一定要多線程同時異步處理,不然真的一
Thumbnail
前言 Vue 在過去的一兩年內快速發展,寫法也一直改變,我在過去一年多也更改了兩次寫法,在這個系列中我將以最新的 composition api + typescript 寫法進行教學。 javascript 有兩大痛點,一個是異步問題,另一個是弱型別。要渲染畫面一定要多線程同時異步處理,不然真的一
Thumbnail
Nuxt.js 是以 Vue 為基底所建構的框架,透過 Nuxt.js,我們能夠更輕鬆地開發靜態頁面 (Static Site)、操作體驗良好的單頁式網站 (SPA)、甚至是顧及 SEO 的伺服器端渲染 (SSR) 網站。
Thumbnail
Nuxt.js 是以 Vue 為基底所建構的框架,透過 Nuxt.js,我們能夠更輕鬆地開發靜態頁面 (Static Site)、操作體驗良好的單頁式網站 (SPA)、甚至是顧及 SEO 的伺服器端渲染 (SSR) 網站。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News