Next.js App Router 提供了三種強大的動態路由(Dynamic Routes),讓我們可以更好的去規劃專案的架構:
[slug]:動態路由 (Dynamic Segment),只能匹配單一層級。[...slug]:捕捉全部路由 (Catch-all Segments) ,可以匹配多個層級(1層或多層),但至少要有一層。[[...slug]]:可選的捕捉全部路由 (Optional Catch-all),可以匹配零層或多層(連根目錄本身都能接)。
app/blog/[slug]/page.tsx中括號內放單一變數名稱。它嚴格要求只能多出一個層級。
/blog/my-first-post → 成功 (slug = "my-first-post")/blog/hello-world → 成功 (slug = "hello-world")/blog → 失敗(404)/blog/react/intro → 失敗(層級太多)取得參數的方式:
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return <div>文章 ID: {slug}</div>;
}
app/shop/[...slug]/page.tsx使用三個點...。它能捕捉後面所有層級的網址,並把它們組合成一個陣列(Array)。但注意:斜線後面必須要有東西(至少要有一段)。
/shop/clothing → 成功 (slug = ['clothing'])/shop/clothing/shirts → 成功 (slug = ['clothing', 'shirts'])/shop → 失敗(因為 ... 要求至少要有一層參數)取得參數的方式:
export default async function Page({ params }: { params: Promise<{ slug: string[] }> }) {
const { slug } = await params; // slug 是一個陣列
return <div>目前路徑標籤: {slug.join(' > ')}</div>;
}
app/docs/[[...slug]]/page.tsx使用雙層中括號[[...slug]]。它比上一種更彈性,連「沒有參數」的根路徑都可以一起接下來處理。
/docs → 成功 (slug = undefined 或不帶參數)
/docs/guide → 成功 (slug = ['guide'])
/docs/guide/getting-started → 成功 (slug = ['guide', 'getting-started'])
常見應用場景:官方文件(Docs)、多層級 Wiki、或是首頁兼內頁的單一頁面渲染。
app/
└── docs/
└── [[...slug]]/
└── page.tsx
app/docs/[[...slug]]/page.tsx
import React from 'react';
interface DocsPageProps {
params: Promise<{
slug?: string[]; // 注意:因為是「可選」的,slug 可能會是 undefined
}>;
}
export default async function DocsPage({ params }: DocsPageProps) {
// 1. 解析非同步的 params
const resolvedParams = await params;
const slug = resolvedParams.slug;
// 2. 情況 A:如果沒有 slug(即訪問 /docs 根目錄)
if (!slug || slug.length === 0) {
return (
<div className="p-8">
<h1 className="text-3xl font-bold">歡迎來到官方文件首頁</h1>
<p className="text-gray-600 mt-2">請從左側選單選擇你想閱讀的技術指南。</p>
</div>
);
}
// 3. 情況 B:有 slug(例如 /docs/react 或 /docs/react/hooks/use-state)
// 我們可以把陣列用斜線接回來,模擬讀取對應的檔案或路由
const currentPath = slug.join(' / ');
return (
<div className="p-8">
<h1 className="text-3xl font-bold">文件路徑:{currentPath}</h1>
<p className="text-sm text-gray-500 mt-1">
目前巢狀深度:{slug.length} 層
</p>
<hr className="my-4" />
<div className="bg-gray-100 p-4 rounded-md">
<h2 className="font-semibold text-lg">除錯資訊(Slug 陣列結構):</h2>
<pre className="mt-2 text-sm">{JSON.stringify(slug, null, 2)}</pre>
</div>
</div>
);
}