測驗:Next.js 頁面路由與導覽
共 5 題,點選答案後會立即顯示結果
1. 在 Next.js App Router 中,什麼決定了網址的結構?
2. 關於 layout.tsx 的敘述,何者正確?
3. 下列哪個資料夾結構可以讓 /docs 和 /docs/api/hooks 都能被訪問?
4. 使用 useRouter hook 時,需要注意什麼?
5. 在動態路由中,當資料不存在時如何顯示 404 頁面?
前言
在前兩篇文章中,我們學會了建立 Next.js 專案並認識了元件的基本結構。現在,是時候讓我們的應用程式「動起來」了——不是指動畫效果,而是讓使用者能在多個頁面之間流暢地切換。
當你用 AI 輔助閱讀 Next.js 專案時,理解路由系統是關鍵的第一步。因為路由決定了整個專案的檔案結構,也決定了使用者能訪問哪些頁面。
App Router 的檔案式路由原理
Next.js 的 App Router 採用「檔案式路由」(File-based Routing),這是一個非常直覺的設計:資料夾結構就是網址結構。
最小範例
app/
├── page.tsx → 對應網址 /
├── about/
│ └── page.tsx → 對應網址 /about
└── blog/
└── page.tsx → 對應網址 /blog
看到這個結構,你馬上就能知道這個網站有三個頁面:首頁、關於頁、部落格頁。
關鍵規則
- 資料夾 = 路由段落:每個資料夾名稱對應網址的一個部分
- page.tsx = 頁面入口:只有資料夾內有
page.tsx才能被訪問 - 沒有 page.tsx = 不可訪問:資料夾可以只用來組織結構,不一定要有頁面
這代表當你看到一個陌生的 Next.js 專案時,只要打開 app/ 目錄,就能快速掌握整個網站的頁面架構。
page.tsx 與 layout.tsx 的作用
page.tsx:頁面內容
page.tsx 定義了該路由的實際內容:
// app/about/page.tsx
export default function AboutPage() {
return (
<div>
<h1>關於我們</h1>
<p>這是關於頁面的內容</p>
</div>
)
}
Code language: JavaScript (javascript)當你讀到一個 page.tsx 檔案時,問自己:「這個頁面要顯示什麼?」
layout.tsx:共用外框
layout.tsx 定義了該路由及其子路由共用的外框:
// app/layout.tsx(根 layout,必須存在)
export default function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-TW">
<body>
<nav>網站導覽列</nav>
<main>{children}</main>
<footer>網站頁尾</footer>
</body>
</html>
)
}
Code language: JavaScript (javascript)重要特性:
children會被替換成當前頁面的內容- Layout 在頁面切換時不會重新渲染,狀態會被保留
- 根目錄的
layout.tsx是必要的,且必須包含<html>和<body>標籤
讀 code 時的思考方式
當你看到一個 layout.tsx,問自己:
- 「這個 layout 包住了哪些頁面?」
- 「哪些 UI 元素是這些頁面共用的?」
巢狀路由結構
App Router 的強大之處在於 layout 可以層層巢狀:
app/
├── layout.tsx ← 根 layout(導覽列、頁尾)
├── page.tsx ← 首頁
└── dashboard/
├── layout.tsx ← Dashboard layout(側邊欄)
├── page.tsx ← /dashboard
├── settings/
│ └── page.tsx ← /dashboard/settings
└── profile/
└── page.tsx ← /dashboard/profile
// app/dashboard/layout.tsx
export default function DashboardLayout({
children
}: {
children: React.ReactNode
}) {
return (
<div className="dashboard">
<aside>Dashboard 側邊欄</aside>
<section>{children}</section>
</div>
)
}
Code language: JavaScript (javascript)當使用者訪問 /dashboard/settings 時,渲染結果會是:
RootLayout
└── DashboardLayout
└── SettingsPage
三層結構自動組合在一起。讀懂這個機制後,你就能理解為什麼某些 UI 元素會「神奇地」出現在每個頁面上。
動態路由:[slug] 與 […slug]
實際應用中,我們常需要根據資料動態生成頁面,例如部落格文章、商品詳情頁。
基本動態路由 [slug]
用方括號包住資料夾名稱,就建立了動態路由:
app/
└── blog/
└── [slug]/
└── page.tsx ← 對應 /blog/任意值
// app/blog/[slug]/page.tsx
export default async function BlogPost({
params
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
return <h1>文章:{slug}</h1>
}
Code language: JavaScript (javascript)對應關係:
| 網址 | params.slug |
|---|---|
/blog/hello-world |
"hello-world" |
/blog/nextjs-tutorial |
"nextjs-tutorial" |
Catch-all 路由 […slug]
用 [...slug] 可以匹配多層路徑:
app/
└── docs/
└── [...slug]/
└── page.tsx ← 對應 /docs/任意/多層/路徑
// app/docs/[...slug]/page.tsx
export default async function DocsPage({
params
}: {
params: Promise<{ slug: string[] }>
}) {
const { slug } = await params
// slug 是陣列
return <p>路徑:{slug.join(' / ')}</p>
}
Code language: JavaScript (javascript)對應關係:
| 網址 | params.slug |
|---|---|
/docs/getting-started |
["getting-started"] |
/docs/api/hooks/useRouter |
["api", "hooks", "useRouter"] |
Optional Catch-all [[…slug]]
雙層方括號讓路由變成可選的,連根路徑也能匹配:
app/
└── shop/
└── [[...slug]]/
└── page.tsx ← 對應 /shop 以及 /shop/任意/路徑
| 網址 | params.slug |
|---|---|
/shop |
undefined |
/shop/clothes |
["clothes"] |
/shop/clothes/shirts |
["clothes", "shirts"] |
讀 code 時的判斷技巧
看到動態路由時,快速判斷:
[id]:單一動態值,params 是字串[...slug]:多層路徑,params 是陣列,不匹配根路徑[[...slug]]:多層路徑,params 是可選陣列,也匹配根路徑
Link 元件與 useRouter Hook
Link 元件:宣告式導覽
<Link> 是 Next.js 提供的導覽元件,用於取代 HTML 的 <a> 標籤:
import Link from 'next/link'
export default function Navigation() {
return (
<nav>
<Link href="/">首頁</Link>
<Link href="/about">關於</Link>
<Link href="/blog/my-first-post">第一篇文章</Link>
</nav>
)
}
Code language: JavaScript (javascript)Link 的優勢:
- 自動預先載入(Prefetch):當連結進入視野,Next.js 會預先載入目標頁面
- 客戶端導覽:不會整頁重新載入,只更新變動的部分
- 保留狀態:共用的 layout 不會重新渲染
useRouter Hook:程式化導覽
當你需要根據某些條件(如表單送出後)進行導覽時,使用 useRouter:
'use client' // 必須是 Client Component
import { useRouter } from 'next/navigation'
export default function LoginForm() {
const router = useRouter()
const handleSubmit = async () => {
// 登入邏輯...
// 登入成功後導向 dashboard
router.push('/dashboard')
}
return (
<button onClick={handleSubmit}>
登入
</button>
)
}
Code language: JavaScript (javascript)useRouter 常用方法
const router = useRouter()
// 導向新頁面(加入瀏覽記錄)
router.push('/dashboard')
// 取代當前頁面(不加入瀏覽記錄)
router.replace('/login')
// 重新整理當前頁面(重新取得伺服器資料)
router.refresh()
// 上一頁
router.back()
// 下一頁
router.forward()
Code language: JavaScript (javascript)讀 code 時的選擇判斷
- 看到
<Link>:這是使用者點擊觸發的導覽 - 看到
router.push():這是程式邏輯觸發的導覽 - 看到
router.replace():導覽後不希望使用者按「上一頁」回來
404 頁面處理
全域 404 頁面
在 app/ 目錄下建立 not-found.tsx:
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
<div>
<h2>找不到頁面</h2>
<p>您要找的頁面不存在</p>
<Link href="/">回到首頁</Link>
</div>
)
}
Code language: JavaScript (javascript)這會處理所有不存在的路由。
在動態路由中觸發 404
當資料不存在時,可以主動觸發 404:
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
export default async function BlogPost({
params
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const post = await getPost(slug)
// 如果文章不存在,顯示 404
if (!post) {
notFound()
}
return <article>{post.content}</article>
}
Code language: JavaScript (javascript)局部 not-found.tsx
你也可以在特定路由段落建立 not-found.tsx,提供更精確的 404 體驗:
app/
├── not-found.tsx ← 全域 404
└── blog/
├── not-found.tsx ← 部落格專用 404
└── [slug]/
└── page.tsx
實際專案中的路由結構範例
app/
├── layout.tsx ← 根 layout
├── page.tsx ← 首頁 /
├── not-found.tsx ← 404 頁面
├── (auth)/ ← 路由群組(不影響 URL)
│ ├── login/
│ │ └── page.tsx ← /login
│ └── register/
│ └── page.tsx ← /register
├── dashboard/
│ ├── layout.tsx ← Dashboard layout
│ ├── page.tsx ← /dashboard
│ └── [teamId]/
│ ├── page.tsx ← /dashboard/team-123
│ └── settings/
│ └── page.tsx ← /dashboard/team-123/settings
└── blog/
├── page.tsx ← /blog(文章列表)
└── [slug]/
└── page.tsx ← /blog/my-post
補充:括號開頭的資料夾 (auth) 是「路由群組」,用來組織檔案但不會影響 URL 結構。
重點整理
| 概念 | 說明 |
|---|---|
| 檔案式路由 | 資料夾結構 = 網址結構 |
| page.tsx | 定義頁面內容,必須存在才能訪問該路由 |
| layout.tsx | 定義共用外框,會包住子路由 |
| [slug] | 動態路由,匹配單一值 |
| […slug] | Catch-all 路由,匹配多層路徑 |
| [[…slug]] | Optional Catch-all,也匹配根路徑 |
| Link | 宣告式導覽,自動預先載入 |
| useRouter | 程式化導覽,需要 ‘use client’ |
| not-found.tsx | 處理 404 錯誤 |
下一步
現在你已經掌握了 Next.js 的路由系統,能夠:
- 快速理解專案的頁面結構
- 分辨靜態路由與動態路由
- 看懂 Link 和 useRouter 的使用時機
下一篇我們將學習資料取得的方式,了解 Server Component 如何直接在伺服器端取得資料。
參考資源
- Next.js 官方文件 – Layouts and Pages
- Next.js 官方文件 – Dynamic Routes
- Next.js 官方文件 – Linking and Navigating
- Next.js 官方文件 – useRouter
- Next.js 官方文件 – not-found.js
進階測驗:Next.js 頁面路由與導覽
共 5 題,包含情境題與錯誤診斷題。