Next.js 動態路由與 Catch-all
當你無法事先知道確定的路由路徑(例如部落格文章的 ID 或產品清單)時,你需要使用 動態路由 (Dynamic Routes)。Next.js 讓你能透過資料夾命名的慣例,輕鬆定義動態參數。
基礎動態路由 [slug]
使用方括號 [] 包裹資料夾名稱,即可建立動態分段。
範例:
app/blog/[slug]/page.tsx 會對應到 /blog/hello-world 或 /blog/nextjs-tutorial。
在頁面元件中,你可以透過 params 屬性取得該參數:
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return <h1>文章內容:{params.slug}</h1>;
}
多重動態路由
你可以在同一個路徑中組合多個動態分段。
範例:
app/shop/[category]/[item]/page.tsx 會對應到 /shop/shoes/nike-air-max。
params 會是 { category: 'shoes', item: 'nike-air-max' }。
Catch-all 路由 [...slug]
如果你想匹配後續所有的層級,可以在方括號內加上三個點 ...。
範例:
app/docs/[...slug]/page.tsx 會對應到:
/docs/introduction/docs/setup/macos/docs/api/auth/providers
此時 params.slug 會是一個字串陣列:['api', 'auth', 'providers']。
Optional Catch-all [[...slug]]
一般的 Catch-all 路由必須至少包含一個子路徑。如果你希望連根路徑也能匹配,可以使用雙重方括號 [[]]。
比較:
[...slug]:匹配/docs/a,但不匹配/docs。[[...slug]]:匹配/docs/a,也匹配/docs。
這在建立複雜的文檔導航或 CMS 系統時非常有用。
程式碼範例:
當使用 Optional Catch-all 時,若使用者訪問的是根路徑(如 /docs),params.slug 的值會是 undefined(或空陣列,視 Next.js 版本與處理方式而定,但在 Server Component 中通常需作空值檢查)。
// app/docs/[[...slug]]/page.tsx
export default function DocsPage({ params }: { params: { slug?: string[] } }) {
// 如果訪問 /docs,params.slug 為 undefined
// 如果訪問 /docs/intro,params.slug 為 ['intro']
const slug = params.slug || [];
if (slug.length === 0) {
return <h1>文檔首頁</h1>;
}
return <h1>正在閱讀:{slug.join(' / ')}</h1>;
}
| 路由路徑 | 網址 URL | params 結果 |
|---|---|---|
app/docs/[[...slug]]/page.tsx | /docs | {} (slug 為 undefined) |
app/docs/[[...slug]]/page.tsx | /docs/intro | { slug: ['intro'] } |
app/docs/[[...slug]]/page.tsx | /docs/intro/setup | { slug: ['intro', 'setup'] } |
生成靜態參數 (generateStaticParams)
如果你使用動態路由,但希望在編譯時就先生成好靜態頁面(Statoc Site Generation, SSG),你可以導出 generateStaticParams 函式。這能極大地提升效能,因為頁面在要求時就已經是準備好的 HTML。
函式用途與語法
generateStaticParams 必須回傳一個物件陣列,每個物件代表一個路由的參數組合。
// app/blog/[slug]/page.tsx
// 1. 定義要生成的靜態路徑列表
export async function generateStaticParams() {
// 模擬從資料庫或 API 取得所有文章
const posts = await fetch('https://api.example.com/posts').then((res) => res.json());
// 回傳物件陣列,每個物件的 key 必須對應動態路由的參數名稱 (如 slug)
return posts.map((post) => ({
slug: post.slug,
}));
}
// 2. 頁面組件
export default function BlogPost({ params }: { params: { slug: string } }) {
return <h1>文章 ID: {params.slug}</h1>;
}
控制未生成的路徑 (dynamicParams)
預設情況下,如果使用者訪問了一個不在 generateStaticParams 回傳列表中的路徑,Next.js 會嘗試在伺服器端即時生成該頁面 (On-demand Rendering)。
你可以透過導出 dynamicParams 變數來控制這個行為:
// true (預設值): 當訪問未生成的路徑時,嘗試即時生成頁面
// false: 當訪問未生成的路徑時,直接回傳 404
export const dynamicParams = false;
export async function generateStaticParams() {
return [{ slug: 'a' }, { slug: 'b' }];
}
true(預設):適合部落格文章,新文章發布後不需要重新部署即可被訪問。false:適合內容固定的網站,確保只有預定義的路徑可以被訪問。
generateStaticParams 來優化核心關鍵指標 (Core Web Vitals),並根據需求設定 dynamicParams 以平衡構建時間與使用者體驗。