Next.js 路由機制
Next.js 提供了多種路由方式,適用於不同場景。本回答將涵蓋:
-
Pages Router:基於 pages 資料夾的傳統路由系統。
-
App Router:Next.js 13+ 引入的進階路由系統,基於 app 資料夾與 React Server Components。
-
Dynamic Routes:處理動態參數的路由,適用於 Pages Router 和 App Router。
-
Intercepting Routes:攔截特定路由以顯示模態視窗或覆蓋內容。
-
Parallel Routes:同時渲染多個獨立內容區域,適用於複雜佈局。
1. Pages Router
Pages Router 是 Next.js 早期的路由系統,基於檔案系統,位於 pages 資料夾的檔案直接對應 URL 路徑。雖然簡單,但功能較有限,Next.js 13+ 更推薦使用 App Router。
特色:
-
每個 .js/.jsx 檔案對應一個路由,例如 pages/about.js 對應 /about。
-
支援動態路由,透過 [param].js 處理動態參數。
-
適合小型專案或快速原型開發。
程式碼範例:
檔案結構:
my-next-app/
├── pages/
│ ├── index.js
│ ├── about.js
│ └── post/
│ └── [id].js
pages/index.js(首頁):
// pages/index.js
import React from "react";
export default function Home() {
return (
<div>
<h1>歡迎來到我的 Next.js 應用程式!</h1>
<p>這是首頁</p>
</div>
);
}
pages/about.js(關於頁面):
// pages/about.js
import React from "react";
export default function About() {
return (
<div>
<h1>關於我們</h1>
<p>這是關於頁面</p>
</div>
);
}
pages/post/[id].js(動態路由):
// pages/post/[id].js
import React from "react";
import { useRouter } from "next/router";
export default function Post() {
const router = useRouter();
const { id } = router.query;
return (
<div>
<h1>文章頁面</h1>
<p>文章 ID: {id}</p>
</div>
);
}
執行結果:
-
訪問 http://localhost:3000/ 顯示首頁。
-
訪問 http://localhost:3000/about 顯示關於頁面。
-
訪問 http://localhost:3000/post/1 顯示「文章 ID: 1」。
優點與缺點:
-
優點:簡單直觀,適合初學者。
-
缺點:功能有限,與新功能(如伺服器組件)相容性差。
2. App Router
App Router 是 Next.js 13+ 引入的路由系統,基於 app 資料夾與 React Server Components,支援進階功能如伺服器端渲染、佈局和資料獲取。
特色:
-
每個資料夾對應一個路由段,page.js 定義頁面內容。
-
支援 layout.js(共用佈局)、loading.js(載入狀態)等特殊檔案。
-
支援進階路由功能,如攔截路由和平行路由。
程式碼範例:
檔案結構:
my-next-app/
├── app/
│ ├── layout.js
│ ├── page.js
│ └── about/
│ └── page.js
app/layout.js(共用佈局):
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="zh-TW">
<body>
<header>
<h1>我的 Next.js 應用程式</h1>
<nav>
<a href="/">首頁</a> | <a href="/about">關於</a>
</nav>
</header>
{children}
</body>
</html>
);
}
app/page.js(首頁):
// app/page.js
export default function Home() {
return (
<div>
<h1>歡迎來到首頁!</h1>
<p>這是使用 App Router 的首頁</p>
</div>
);
}
app/about/page.js(關於頁面):
// app/about/page.js
export default function About() {
return (
<div>
<h1>關於我們</h1>
<p>這是使用 App Router 的關於頁面</p>
</div>
);
}
執行結果:
-
訪問 http://localhost:3000/ 顯示首頁,包含共用佈局。
-
訪問 http://localhost:3000/about 顯示關於頁面。
優點與缺點:
-
優點:支援伺服器組件、進階功能,效能更好。
-
缺點:學習曲線較高,與 Pages Router 不相容。
3. Dynamic Routes(動態路由)
Dynamic Routes 是 App Router(和 Pages Router)中處理動態參數的路由方式,透過 [param] 或 [...param] 定義動態路徑。這裡專注於 App Router 的實現,並包含進階用法。
特色:
-
[param]:單一動態參數,例如 /post/1。
-
[...param]:捕捉多層路徑(Catch-all Routes),例如 /post/2023/10/01。
-
[[...param]]:可選 Catch-all Routes,允許路徑為空。
程式碼範例:
檔案結構:
my-next-app/
├── app/
│ ├── post/
│ │ ├── [id]/
│ │ │ └── page.js
│ │ └── [...slug]/
│ │ └── page.js
│ └── layout.js
app/post/[id]/page.js(單一動態參數):
// app/post/[id]/page.js
export default function Post({ params }) {
const { id } = params;
return (
<div>
<h1>文章頁面</h1>
<p>文章 ID: {id}</p>
</div>
);
}
app/post/[...slug]/page.js(捕捉多層路徑):
// app/post/[...slug]/page.js
export default function PostSlug({ params }) {
const { slug } = params;
return (
<div>
<h1>多層路徑文章</h1>
<p>路徑: {slug ? slug.join("/") : "無路徑"}</p>
</div>
);
}
app/post/[[...slug]]/page.js(可選 Catch-all Routes,可選):
// app/post/[[...slug]]/page.js
export default function PostOptionalSlug({ params }) {
const { slug } = params;
return (
<div>
<h1>可選路徑文章</h1>
<p>路徑: {slug ? slug.join("/") : "無路徑"}</p>
</div>
);
}
執行結果:
-
訪問 http://localhost:3000/post/1 顯示「文章 ID: 1」。
-
訪問 http://localhost:3000/post/2023/10/01 顯示「路徑: 2023/10/01」。
-
訪問 http://localhost:3000/post(若使用 [[...slug]])顯示「路徑: 無路徑」。
優點與缺點:
-
優點:靈活處理動態參數,適用於博客、電子商務等場景。
-
缺點:需自行處理參數邏輯,可能增加複雜度。
4. Intercepting Routes(攔截路由)
Intercepting Routes 是 App Router 的進階功能,允許在不改變 URL 的情況下攔截特定路由並渲染不同內容,常用于模態視窗或覆蓋式頁面。
特色:
-
使用 (.) 攔截同層級路由,(..) 攔截上一層級路由。
-
常與客戶端導航(Link 或 useRouter)結合,提供模態式體驗。
程式碼範例:
假設你有一個博客網站,/posts 顯示文章列表,點擊文章時以模態視窗顯示詳情。
檔案結構:
my-next-app/
├── app/
│ ├── posts/
│ │ ├── (.)post/
│ │ │ └── [id]/
│ │ │ └── page.js
│ │ └── page.js
│ └── layout.js
app/posts/page.js(文章列表):
// app/posts/page.js
import Link from "next/link";
export default function Posts() {
const posts = [
{ id: 1, title: "第一篇文章" },
{ id: 2, title: "第二篇文章" },
];
return (
<div>
<h1>文章列表</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/post/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
</div>
);
}
app/posts/(.)post/[id]/page.js(攔截文章詳情):
// app/posts/(.)post/[id]/page.js
import Link from 'next/link';
export default function PostModal({ params })26: { id } = params;
return (
<div
style={{
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
background: 'white',
padding: '20px',
border: '1px solid #ccc',
zIndex: 1000,
}}
>
<h1>文章詳情 (模態視窗)</h1>
<p>文章 ID: {id}</p>
<Link href="/posts">關閉</Link>
</div>
);
}
執行結果:
-
訪問 http://localhost:3000/posts 顯示文章列表。
-
點擊「第一篇文章」,URL 顯示為 /post/1,但頁面顯示模態視窗(文章詳情)。
-
點擊「關閉」返回文章列表。
優點與缺點:
-
優點:無需改變 URL,提供模態式體驗。
-
缺點:設定較複雜,需熟悉路由匹配邏輯。
5. Parallel Routes(平行路由)
Parallel Routes 是 App Router 的進階功能,透過 @folder 命名資料夾,允許在同一路由下同時渲染多個獨立內容區域,適合複雜佈局如儀表板。
特色:
-
每個 @folder 定義一個獨立內容區域,透過 layout.js 的插槽(slot)組合。
-
支援獨立的錯誤處理和載入狀態。
-
可與動態路由結合。
程式碼範例:
假設你正在構建一個儀表板,包含側邊欄(@sidebar)和主內容(@main)。
檔案結構:
my-next-app/
├── app/
│ ├── dashboard/
│ │ ├── @sidebar/
│ │ │ └── page.js
│ │ ├── @main/
│ │ │ └── [userId]/
│ │ │ └── page.js
│ │ └── layout.js
│ └── layout.js
app/dashboard/layout.js(平行路由佈局):
// app/dashboard/layout.js
export default function DashboardLayout({ children, sidebar, main }) {
return (
<div style={{ display: "flex" }}>
<div style={{ width: "200px", background: "#f0f0f0", padding: "20px" }}>
{sidebar}
</div>
<div style={{ flex: 1, padding: "20px" }}>{main}</div>
{children}
</div>
);
}
app/dashboard/@sidebar/page.js(側邊欄):
// app/dashboard/@sidebar/page.js
export default function Sidebar() {
return (
<div>
<h2>側邊欄</h2>
<ul>
<li>
<a href="/dashboard">首頁</a>
</li>
<li>
<a href="/dashboard/settings">設定</a>
</li>
</ul>
</div>
);
}
app/dashboard/@main/[userId]/page.js(主內容,結合動態路由):
// app/dashboard/@main/[userId]/page.js
export default function UserMain({ params }) {
const { userId } = params;
return (
<div>
<h2>使用者主內容</h2>
<p>使用者 ID: {userId}</p>
</div>
);
}
執行結果:
-
訪問 http://localhost:3000/dashboard/123 顯示儀表板,側邊欄顯示導航選單,主內容顯示「使用者 ID: 123」。
-
children(page.js)可作為額外內容區域,若無需可省略。
優點與缺點:
-
優點:支援複雜佈局,模組化設計。
-
缺點:需設計清晰的插槽結構,初學者可能較難上手。
總結比較
路由方式 | 用途 | 優點 | 缺點 |
---|---|---|---|
Pages Router | 小型專案、快速原型 | 簡單直觀,易上手 | 功能有限,與新功能相容性差 |
App Router | 進階應用、伺服器組件 | 支援伺服器組件、進階功能,效能更好 | 學習曲線較高,需調整舊程式碼 |
Dynamic Routes | 處理動態參數與多層路徑 | 靈活,支援單一與多層參數 | 需自行處理參數邏輯 |
Intercepting Routes | 攔截路由以顯示模態視窗或覆蓋內容 | 無需改變 URL,模態式體驗 | 設定複雜,需熟悉路由匹配 |
Parallel Routes | 同時渲染多個獨立內容區域 | 支援複雜佈局,模組化 | 需清晰的插槽結構,初學者較難上手 |
實作建議與操作步驟
-
環境設定:
-
安裝 Next.js:npx create-next-app@latest my-next-app
-
進入專案:cd my-next-app
-
啟動開發伺服器:npm run dev
-
-
選擇路由方式:
-
初學者:從 Pages Router 開始,熟悉檔案對應路由的簡單邏輯。
-
進階需求:使用 App Router,結合伺服器組件與進階功能。
-
動態參數:根據需求選擇 [param] 或 [...param],並妥善處理 params。
-
模態視窗:使用 Intercepting Routes,結合 Link 實現無縫導航。
-
複雜佈局:使用 Parallel Routes,設計清晰的 @folder 結構。
-
-
測試與除錯:
-
在 http://localhost:3000 測試各路由是否正確渲染。
-
使用 console.log(params) 檢查動態參數是否正確傳遞。
-
確保 layout.js 正確組合平行路由的插槽。
-
常見問題與解決方法
-
路由未正確顯示:
-
檢查檔案名稱是否正確(例如 [id] 而非 (id))。
-
確保 layout.js 正確傳遞 children 或插槽。
-
-
動態參數未傳遞:
-
在 page.js 中使用 console.log(params) 檢查參數。
-
確認資料夾名稱使用 [param] 或 [...param]。
-
-
模態視窗樣式異常:
- 檢查 style 屬性,確保 position: fixed 和 zIndex 設定正確。
-
平行路由未渲染:
- 確認 layout.js 正確定義插槽(例如 sidebar、main)。