生成 pages 和 routing
在 Vue 中,我們會使用到 Vue Router 來實現切換頁面或路由的需求,而在 Nuxt 3 中,預設是沒有使用路由相關套件,直至建立了
pages目錄,Nuxt 將會自動載入 Vue Router 來管理路由,並且具有一定的規則需要遵循,以下將介紹頁面目錄與路由之間的關係。
建立 page
建立單層 url
當我們建立 ./pages/index.vue,檔案內容如下,則表示路由 / 對應到這個頁面檔案,我們只需要建立檔案,路由的配置將會由 Nuxt 3 自動產生。
<template>
<div class="bg-white py-24">
<div class="flex flex-col items-center">
<h1 class="text-6xl font-semibold text-gray-800">這裡是首頁</h1>
</div>
</div>
</template>
Nuxt 3 我們需要使用 <NuxtPage /> 來顯示我們建立的路由頁面,這裡非常重要,否則路由及頁面將無法正確運作。
修改 ./app.vue,檔案內容如下:
<template>
<div>
<NuxtPage />
</div>
</template>
建立多層 url
在 pages 下建立一個 docs.vue 表示對應路由 /docs,也可以將檔案放置在 docs 目錄下並重新命名為 index.vue 即 ./pages/docs/index.vue,這樣也可以透過 /docs 瀏覽到相同的頁面。
但是不建議多一層,可以直接用 docs.vue 就可以不用多一層,如果是裡面還有其它的 url 放裡面,就比較沒有差別
自動產生的路由
如果你有興趣想看看 Nuxt 自動產生出來的路由配置長什麼樣子,可以使用 npm run build 或 npx nuxt build 來建構出 .output 目錄,並打開 .output/server/chunks/app/server.mjs,搜尋 const _routes = 或剛剛建立的檔案名稱 about.vue,就可以找到下面這一段程式碼:
const _routes = [
{
name: "error_404",
path: "/:error_404",
file: "/Users/chuchengyang/techDesign/nuxt-app/src/pages/[error_404].vue",
children: [],
meta: meta$a,
alias: [],
component: () => import('./_nuxt/_error_404_.029246cc.mjs').then((m) => m.default || m)
},
{
name: "index",
path: "/",
file: "/Users/chuchengyang/techDesign/nuxt-app/src/pages/index.vue",
children: [],
meta: meta$9,
alias: [],
component: () => import('./_nuxt/index.26613ec5.mjs').then((m) => m.default || m)
},
{
name: "market-cart",
path: "/market/cart",
file: "/Users/chuchengyang/techDesign/nuxt-app/src/pages/market/cart.vue",
children: [],
meta: meta$8,
alias: (meta$8 == null ? void 0 : meta$8.alias) || [],
component: () => import('./_nuxt/cart.61d73f5c.mjs').then((m) => m.default || m)
},
...
];
這段程式碼與 Vue 中的路由配置非常相像,其實這就是 Nuxt 3 檢測到 pages 目錄,自動幫我們載入 Vue Router 與依據 pages 目錄下的檔案結構,自動產生出所需的路由配置。
建立路由連結
Nuxt 3 的路由中,是使用 <NuxtLink> 來建立路由連結來進行頁面的跳轉,我們嘗試在首頁新增幾個路由連結來進行頁面導航。
<template>
<div class="bg-white py-24">
<div class="flex flex-col items-center">
<h1 class="text-6xl font-semibold text-gray-800">這裡是首頁</h1>
<div class="my-4 flex space-x-4">
<NuxtLink to="'/about'">前往 About</NuxtLink>
<NuxtLink to="{name: 'contact'}">前往 Contact</NuxtLink>
</div>
</div>
</div>
</template>
帶參數的動態路由匹配
在實務上,我們可能需要將路徑作為參數傳遞給同一個元件,例如,我們有一個 users 頁面元件,在 /users/ryan 或 /users/jennifer 路徑,都能匹配到同一個 users 元件,並將 ryan 或 jennifer 當作參數傳遞給 users 頁面元件使用,那麼我們就需要動態路由來做到這件事。
{
name: "users",
path: "/users/:id",
component: "./pages/users.vue",
}
這樣我們就能達到進入 /users/ryan 路由將 ryan 當作 id 參數傳入 users 元件中,路徑參數用冒號 : 表示,這個被匹配的參數 (params),會在元件中可以使用 useRoute() 與 route.params.id 取得。
在 Nuxt 3 中,我們要實現這個效果,需要將檔案名稱添加中括號 [],其中放入欲設定的參數名稱,譬如下面的目錄結構與檔案名稱。
./pages/
└── users/
└── [id].vue
我們在 script 就可以從 route.params 拿到我們所設定的參數名稱 id,並將其在 template 中渲染出來。瀏覽 http://localhost:3000/users/ryan ,看看效果,Nuxt 3 就能匹配到使用者的 id 參數 ryan,並傳入users 頁面元件。
<script setup>
const route = useRoute()
const { id } = route.params
</script>
你也可以在 template 直接使用 {{ $route.params.id }} 來渲染出 id 參數。
匹配所有層級的路由
如果你需要匹配某個頁面下的所有層級的路由,你可以在參數前面加上 ... ,例如,[...slug].vue,這將匹配該路徑下的所有路由。
建立 ./catch-all/[...slug].vue 檔案:
./pages/
└── catch-all/
└── [...slug].vue
./catch-all/[...slug].vue 檔案內容如下:
<template>
<div class="bg-white py-24">
<div class="flex flex-col items-center">
<h1 class="text-4xl text-gray-800">這是 catch-all/... 下的頁面</h1>
<p class="mt-8 text-3xl text-gray-600">匹配到的 Params:</p>
<p class="my-4 text-5xl font-semibold text-violet-500">{{ $route.params.slug }}</p>
<span class="text-xl text-gray-400">每個陣列元素對應一個層級</span>
</div>
</div>
</template>
我們可以輸入 /catch-all/hello 及 /catch-all/hello/world,路由的參數 slug 就會是一個陣列,陣列的每個元素對應每一個層級。
畫面會得到一個 array
// /catch-all/hello => ['hello']
// /catch-all/hello/wor => ['hello', 'world']
建立 404 Not Found 頁面
Nuxt 3 提供一個配置來處理 404 Not Found 的頁面,當我們建立 ./pages/404.vue 頁面, Nuxt 3 所有未匹配的路由,將會交由這個頁面元件做處理,並同時設定 404 HTTP Status Code。
./pages/404.vue
它是特別匹配的,所以和上面的邏輯不同,只是要 routers 裡面找不到的,都會到此 pages
建立多層的目錄結構
如果理解了動態路由的中括號 [] 用法,那我們就可以建立更複雜的頁面目錄結構:
./pages/
└── posts/
├── [postId]/
│ ├── comments/
│ │ └── [commentId].vue
│ └── index.vue
├── index.vue
└── top-[number].vue
**它也是可以像
top-[number].vue這樣子,變數前面還有一個 prefix 的 url **
巢狀路由 (Nested Routes)
巢狀路由 (Nested Routes) 或稱嵌套路由,顧名思義,當我們想要在一個頁面鑲嵌另一個頁面時,就需要巢狀路由來幫助我們。
例如,我們想要在 docs 頁面元件中顯示 doc-1 或 doc-2 頁面元件,並在切換 doc-1 或 doc-2 頁面時,只是在 docs 下的嵌套頁面進行切換。
/docs/doc-1 /docs/doc-2
+------------------+ +-----------------+
| docs | | docs |
| +--------------+ | | +-------------+ |
| | doc-1 | | +------------> | | doc-2 | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
在 Vue 3 使用 Vue Router 實作上述巢狀路由時,即 docs 頁面要能顯示 doc-1,我們在路由配置可能就會寫 path: '/docs' 與 children,並在 children 加入 path: '/doc-1',其中 docs 頁面包含 <router-view />,最終瀏覽路由路徑 /docs/doc-1 就可以看到嵌套頁面的效果。
{
path: '/docs',
component: () => import('./pages/docs.vue')
children: [
{
path: 'doc-1',
component: () => import('./pages/docs/doc-1.vue')
}
]
}
而在 Nuxt 3 頁面的約定式路由機制下,我們即是透過目錄結構與頁面元件實做出嵌套路由的效果。 舉例來說,當我們建立了下面的目錄頁面結構:
這裡需要注意,一定要有 docs.vue 與 docs 同名的目錄
./pages/
├── docs/
│ ├── doc-1.vue
│ └── doc-2.vue
└── docs.vue
頁面元件的參考程式碼如下:
./pages/docs.vue
<template>
<div class="bg-white">
<div class="my-6 flex flex-col items-center">
<h1 class="text-3xl font-semibold text-gray-800">這裡是 Docs</h1>
<div class="my-4 flex space-x-4">
<NuxtLink to="/docs/doc-1">前往 Doc 1</NuxtLink>
<NuxtLink to="/docs/doc-2">前往 Doc 2</NuxtLink>
</div>
</div>
<div class="border-b-2 border-gray-100" />
<div class="flex flex-col items-center">
<NuxtPage />
</div>
</div>
</template>
./pages/doc-1.vue
<template>
<div class="flex flex-col items-center">
<p class="my-8 text-3xl text-blue-500">這是我的第一份文件</p>
</div>
</template>
./pages/doc-2.vue
<template>
<div class="flex flex-col items-center">
<p class="my-8 text-3xl text-green-500">這是我的第二份文件</p>
</div>
</template>
Nuxt 3 在自動生成路由時,實際上幫我們做出了類似這樣子的路由結構:
{
name: "docs",
path: "/docs",
component: "./pages/docs.vue",
children: [
{
name: "docs-first-doc",
path: "doc-1",
component: "./pages/docs/doc-1.vue",
}
],
}
一定要記得在 docs 頁面加上 <NuxtPage />,來作為顯示巢狀頁面的容器,接著分別瀏覽 /docs、 /docs/doc-1 與 /docs/doc-2,可以發現在兩個頁面中上方的皆有顯示標題「這裡是 Docs」,該文字是由 docs.vue 元件提供的標題文字,而頁面下方則是 doc-1 與 doc-2 子頁面顯示的地方,以此就可以實現巢狀路由效果囉!