Next.js Parallel Routes 與 Intercepting Routes

在處理複雜的網頁應用程式(如管理後台或社交媒體)時,基礎的路由機制有時不足以應付多樣化的 UI 需求。Next.js 提供了兩個強大的工具:平行路由攔截路由

平行路由 (Parallel Routes)

平行路由允許你在同一個佈局 (Layout) 中,同時顯不同的頁面。這在構建儀表板 (Dashboard) 時特別有用。

核心運作方式:Slots

你透過在資料夾前加上 @ 符號來定義一個 Slot

目錄結構:

app/
  dashboard/
    layout.tsx
    @analytics/    # Slot 1
      page.tsx
    @team/         # Slot 2
      page.tsx

layout.tsx 中,這些 Slots 會作為 Props 傳入:

// app/dashboard/layout.tsx
export default function Layout({
  children, // 對應 app/dashboard/page.tsx
  analytics, // 對應 app/dashboard/@analytics/page.tsx
  team, // 對應 app/dashboard/@team/page.tsx
}: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  team: React.ReactNode;
}) {
  return (
    <div className="grid grid-cols-2 gap-4">
      <div className="col-span-2">{children}</div>
      <div className="bg-white p-4 shadow">{analytics}</div>
      <div className="bg-white p-4 shadow">{team}</div>
    </div>
  );
}

優點

  • 獨立暫停 (Independent Streaming):每個 Slot 可以有自己的 loading.tsx,不會互相阻塞。
  • 條件渲染:你可以根據權限或狀態來決定是否使用 return null 來隱藏某個 Slot。

預設視圖 (default.tsx)

這是使用平行路由時最容易遇到的坑。

當你導航到一個路由(例如 /dashboard/settings)時,Next.js 會同時嘗試載入所有定義的 Slots(@analytics@team)對應於 /settings 的內容。

如果 @analytics 沒有定義 settings/page.tsx,Next.js 該顯示什麼?

這取決於導航的類型:

  1. 軟導航 (Soft Navigation) - 點擊 <Link> 跳轉: Next.js 會保留該 Slot 當前顯示的內容 (State preserved)。

  2. 硬導航 (Hard Navigation) - 瀏覽器重新整理 (F5): Next.js 無法得知當前的狀態,因此會嘗試尋找 default.tsx

    • 如果有 default.tsx,渲染該內容。
    • 如果沒有 default.tsx,Next.js 會渲染 404,導致整個頁面壞掉。

因此,強烈建議為每個 Slot 建立一個 default.tsx,以確保重新整理時不會出錯:

// app/dashboard/@analytics/default.tsx
export default function Default() {
  // 當路徑不匹配時,顯示這個內容
  // 你可以回傳 null 來什麼都不顯示,或回傳骨架屏
  return null;
}

攔截路由 (Intercepting Routes)

攔截路由讓你可以在當前頁面中「攔截」某個導航跳轉,並顯示特定的 UI(通常是 Modal 或側拉窗),同時瀏覽器的 URL 會更新。

符號規範:

  • (.):匹配同一層級。
  • (..):匹配上一層級。
  • (...):匹配根目錄。

實際案例:照片牆

假設你在瀏覽 /feed 頁面,點擊一張照片。

  • 使用者看到:在當前頁面彈出照片 Modal。
  • URL 變更為/photo/123
  • 如果重新整理頁面:則會直接進入 /photo/123 的完整頁面,而不是 Modal。

目錄結構:

app/
  feed/
    page.tsx      # 照片列表
    (..)photo/    # 攔截層
      [id]/
        page.tsx  # Modal 內容
  photo/
    [id]/
      page.tsx    # 完整頁面內容
// app/feed/(..)photo/[id]/page.tsx
export default function PhotoModal({ params }: { params: { id: string } }) {
  return (
    <div className="fixed inset-0 bg-black/50 flex items-center justify-center">
      <div className="bg-white p-8">這是照片 {params.id} 的彈窗</div>
    </div>
  );
}

組合應用

平行路由與攔截路由經常搭配使用。例如,在 @modal Slot 下定義一個 (.)login 路由,這樣當使用者點擊登入連結時,就能在不離開當前頁面的情況下顯示登入彈窗。

這類複雜路由雖然強大,但設計時請考量 default.tsx 的定義,以處理重新整理或不匹配路徑時的備位畫面。

掌握了這些進階路由模式,你的 Next.js 應用將能擁有媲美原生 App 的流暢體驗。