iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
Modern Web

從 React 學 Next.js:不只要會用,還要真的懂系列 第 23

【Day 23】App Router 的進階用法 2 - Parallel Routes

  • 分享至 

  • xImage
  •  

昨天我們已經看了 Next.js App Router 的 Route Groups 和 Private Folders 的使用方式,今天接著再來看另一個 Page Router 沒有的用法,也就是可以用來設定平行路由的「Parallel Routes」。

Parallel Routes 是什麼?

所謂的「Parallel Routes」指的是就和字面上的意思一樣是「平行路由」的意思,使用「Parallel Routes」可以讓同一個 layout 層級下,透過 @slot 在同一個網址中同時渲染多個「平行路由底下的路由段(Route Segment) 對應的 page 檔案」。

在一般情況下,Next.js 中的每一個路由段 (Route Segment) 只會對應到一個頁面,但若是使用 Parallel Routes 的設定,則可以讓設定為 Parallel Route 的路由段在同一個頁面中,能被獨立載入並且顯示。

「Parallel Routes(平行路由)」是 App Router 提供的一種進階功能,這個功能允許在同一個 Layout 架構下,同時擁有多個彼此獨立的「路由 slot 區塊」。這裡指的 @slot 區塊,就是在一個畫面中被挖空的特定區塊,這些區塊可以被放入不同的內容。雖然使用「Parallel Routes」並不會建立相應的網址,但可以讓同一個頁面中多個 UI 區塊可以各自處理自己的 loading、error 和 default,也能共用 layout 或資料來源。

‼️補充‼️
路由段 (Route Segment) 指的是網址對應到的資料夾節點,例如:有一個檔案路徑為 src/app/category/pants/page.tsx,categorypants 都代表著一個路由段。
更細節的說明也可以參考第十五天有提到過的 Route 和 Route Segment 的定義

單純以文字說明,可能還是有點難理解 Parallel Routes 的概念,這裡就先簡單地呈現一個小例子來說明。

一般情況下,我們設定 Router 後,看到的頁面會是一個路由對應到的就是路由段資料夾底下的 page 檔案。假設有一個檔案是 src/app/page1/page.tsx,如果有實際的畫面,看起來就會像是以下這樣。
https://ithelp.ithome.com.tw/upload/images/20250919/20130914dkQNaheKlC.png

但是如果使用 Parallel Routes,則可以透過呈現以下這個範例畫面,底下的藍色和粉色區塊各自是獨立的路由段,會各各自處理獨立的 loading、error。
https://ithelp.ithome.com.tw/upload/images/20250919/20130914wkBZSklNLX.png

看到這個畫面,可能有些人會有這個疑問:「這個畫面不是可以單純靠元件就組成嗎?為什麼需要另外設定 Parallel Routes 呢?」

接下來我們就來看看差異,也進一步瞭解一下 Parallel Routes 的主要用途是什麼。

Parallel Routes 的主要用途是什麼?它與單純用元件組成畫面的差義是?

其實 Parallel Routes 的主要用途並不單純是用來切分畫面邏輯或 UI 管理,而是讓每個區塊(slot)擁有獨立的路由控制權、錯誤處理與 loading 狀態。

當我們只是以元件的方式在頁面中呈現左右兩個區塊的內容時,如果其中一個元件在資料請求或渲染過程中發生錯誤,整個頁面都會因為這個錯誤而無法正確渲染,尤其是 SSR 階段,會導致整頁都無法正常顯示或整頁顯示錯誤畫面。

但是如果改用 Parallel Routes 的方式來呈現相同分割成左右兩個區塊的畫面,即使其中一個區塊的資料取得失敗,也只會影響對應的區塊,其他區塊仍能正常顯示。這是因為在使用 Parallel Routes 時,看似只是個普通元件的左右兩個區塊的畫面,實際上已被拆成兩個獨立的路由段,彼此擁有各自的 page 檔案、error 檔案、loading 檔案。

這裡用一個實際案例來看看,這是一個有分成兩個區塊的畫面。
https://ithelp.ithome.com.tw/upload/images/20250919/20130914dNV81mybzR.png

當使用一般元件呈現這樣的畫面時,其中一個區塊出現錯誤時,會導致當下頁面直接整個顯示錯誤。

import PartA from "./components/PartA";
import PartB from "./components/PartB";

const normalPage = () => {
  return (
    <div className="flex w-full gap-2">
      <div className="border w-full h-full">
        <PartA />
      </div>
      <div className="border w-full h-full">
        <PartB />
      </div>
    </div>
  );
};

export default normalPage;

https://ithelp.ithome.com.tw/upload/images/20250919/20130914GOlEPxoHyA.png

改成 Parallel Routes 的寫法時,當其中一個區塊出現錯誤時,並不會影響到整個畫面,只有對應的區塊會無法正常顯示。
https://ithelp.ithome.com.tw/upload/images/20250919/20130914AGjxRVYHG8.png

Parallel Routes 的基本用法

了解 Parallel Routes 的用途之後,接著再來看看怎麼在 App Router 中使用 Parallel Routes 這個進階的用法。

∙ 定義 Parallel Routes
想要使用 Parallel Routes,會需要在資料夾名稱中加上 @,每一個名稱有加上 @ 資料夾會被視為一個 named Slot。
https://ithelp.ithome.com.tw/upload/images/20250920/20130914UiJFPMcQla.png

∙ 用 props 把 named slot 帶進 layout 中使用

const DetailPageLayout = ({
  children,
  typeA,
  typeB,
}: {
  children: React.ReactNode;
  typeA: React.ReactNode;
  typeB: React.ReactNode;
}) => {
  return (
    <div className="flex">
      <div className="w-[200px] h-[calc(100vh-60px)] bg-amber-200">
        sidebar menu
      </div>
      <div className="w-full">
        <div className="p-6">{children}</div>
        <div className="flex w-full h-[400px] gap-2 p-6">
          <div className="border w-full h-full">{typeA}</div>
          <div className="border w-full h-full">{typeB}</div>
        </div>
      </div>
    </div>
  );
};

export default DetailPageLayout;

這樣設定後,就可以同時在頁面中使用 typeA 和 typeB 這兩個頁面,且這兩個頁面會使用同一個 layout。
https://ithelp.ithome.com.tw/upload/images/20250919/20130914QatOivWxf9.png

∙ 設定 fallback 的預設頁面
如果要設定一個預設的對應頁面,也可以在名稱帶有 @ 的資料夾下建立名為 default.tsx 的檔案。當輸入網址進入到有使用 @slot 的 layout 時,會去檢查 @slot 底下有無對應到的 page 檔案,如果沒有對應到的 page 檔案,就會 fallback 使用 default 檔案。

舉例來說,如果建立這樣的資料夾結構。
https://ithelp.ithome.com.tw/upload/images/20250920/20130914AzsAosPlVJ.png

在 layout 中有雖然有把 @content 帶入 layout 檔案內,但是當進入 /shop 網址時,會去檢查 @content 底下有沒有 page 檔案,因為目前沒有設定對應的 page 檔案,也就會 fallback 到 default 檔案的內容。
https://ithelp.ithome.com.tw/upload/images/20250920/20130914f9qL5ZczzK.png

等到在 @content 資料夾下,有加上 page.tsx 檔案,並進入這個頁面時,才可以正常顯示 @content 這個 slot 的內容。
https://ithelp.ithome.com.tw/upload/images/20250920/20130914gsee9R2g3L.png

children 和 slot 的關係是?

前面有提到當我們要使用 Parallel Routes 時,就需要先把 slot 透過 props 放在 layout 中我們想要放進的區塊內,例如以下這樣:

const Layout = ({
  children,
  content,
}: {
  children: React.ReactNode;
  content: React.ReactNode;
}) => {
  return (
    <div className="w-full p-4">
      <div className="border p-3">{children}</div>
      <div className="p-6 border-2 border-red-400 mt-3">{content}</div>
    </div>
  );
};

export default Layout;

這時候我們可以再往回思考一下,那這裡的 children 是哪來的?為什麼和我們命名的 slot 是一樣的用法呢?

其實 children 也是透過 slot 產生的,但是是一個隱式的 slot,所以我們並不需要在專案中建立 @children 資料夾就可以使用。實際上 children 屬性是一個隱式插槽,無需對應到特定資料夾。也就是說 app/page.js 就等同於是 app/@children/page.js。所以用法上也才會和我們現在所提到的 named slot 這麼相似。

Parallel Routes 的實作案例

了解完 Parallel Routes ,我們接著來看個適合應用 Parallel Routes 的例子:「Tabs 頁面的呈現」。

這裡先建立一個 types 資料夾當作層的路由,以及 @content 資料夾作為 named slots 的部分。
https://ithelp.ithome.com.tw/upload/images/20250919/20130914pzXresPyBQ.png

@content 內我另外建立了兩個資料夾來對應不同的類別內容
https://ithelp.ithome.com.tw/upload/images/20250919/20130914Ryh8OBf8Cb.png

不論是 types 資料夾還是 @content 裡面的資料夾都加上 page 檔案,另外也把 layout 檔案及 default 檔案都加上。

// src/app/types/layout.tsx
import Link from "next/link";
import LinkTabs from "./components/LinkTabs";

const TypesLayout = ({ content }: { content: React.ReactNode }) => {
  return (
    <div>
      <div className="w-full h-12 bg-green-200 justify-center items-center flex">
        <h1 className="text-2xl">
          <Link href="/">佳佳食堂菜單</Link>
        </h1>
      </div>
      <LinkTabs />
      <div className="p-4">{content}</div>
    </div>
  );
};

export default TypesLayout;

雖然在這個例子中,實際上用不到 children 這個 props,也就是說我們不會使用到 types 資料夾底下的 page 檔案內容,我們在 layout 只會直接使用 named slots 的內容 content,但還是需要設定一個 page 檔案,因為 Next.js 預設還是會去查找 page 檔案的內容,如果沒有設定,就會跳出 404 的錯誤。

// src/app/types/page.tsx
const TypeMainPage = () => {
  return null;
};

export default TypeMainPage;

接著也補上 types 底下的 layout 內容。

// src/app/types/layout.tsx
import Link from "next/link";
import LinkTabs from "./components/LinkTabs";

const TypesLayout = ({ content }: { content: React.ReactNode }) => {
  return (
    <div>
      <div className="w-full h-12 bg-green-200 justify-center items-center flex">
        <h1 className="text-2xl">
          <Link href="/">佳佳食堂菜單</Link>
        </h1>
      </div>
      <LinkTabs />
      <div className="p-4">{content}</div>
    </div>
  );
};

export default TypesLayout;

最後在 noodles 和 rice 資料夾底下補上對應 tab 想要顯示的內容,就完成這個帶有 tabs 的畫面了。
https://i.imgur.com/seM8vTO.gif

完整範例可以參考範例連結

除了這個例子外,同樣也可以套用在概念類似的「左側為選單,右側為選擇到的選單項目對應畫面」的那種畫面組成。

這時一定有人會想說「我們用元件也可以組成這樣的畫面效果啊!為什麼還要刻意用 Parallel Routes 呈現?」

以實際案例反思單純用元件呈現的差異

這裡我們就來聊聊差異是什麼!

當我們使用 Parallel Routes 來呈現時,由於每個頁面都有獨立的 loading、error 頁面,所以當其中一個 tab 對應到的 named slot 頁面出現錯誤時,不會顯示出蓋掉整個畫面的錯誤頁面,原本的用來切換對應頁面的 tabs 部分還是可以正常顯示出來,只有對應到的 named slot 會顯示錯誤內容,也就不會影響使用者的使用,使用者仍然可以透過 tab 去切換到其他沒有錯誤的 tab 頁面。

就像是這個情境,其中一頁有問題,我還是可以切換到另一個 tab 查看畫面及使用,但是記得要加上 error 檔案,才可以正常顯示 error 畫面在出現錯誤的 named slot 上。
https://i.imgur.com/MYHlLcl.gif

除此之外,如果實務上有需要提供對應 tab 的網址時,因為每個 tab 都有一個獨立的路由了,所以也有確切的路由可以使用。也就不需要再透過加上 query string 的方式來區別要顯示哪個 tab 的對應頁面。

總結

Parallel Routes 是 Next.js App Router 提供的一種進階功能,讓同一個 layout 下可以定義多個 named slots,並在同一個頁面中同時渲染多個獨立的路由段。與單純用元件組合畫面不一樣的是「Parallel Routes 的每個 slot 都擁有自己的 page、loading、error、default,因此能呈現獨立的錯誤隔離與載入控制」。其實 children 也是透過 slot 來呈現,但是它不同於 Parallel Routes 的 named slots,是一種隱式的 slot,所以不需要創建出 @childeren 資料夾。


今天的 Parallel Routes 的進階用法就到這裡告一個段落,明天我們會繼續來看另一個 App Router 的進階用法。

參考資料

官方文件 - parallel-routes


上一篇
【Day 22】Next.js App Router 的進階用法 1 - Route Groups 和 Private Folders
下一篇
【Day 24】App Router 的進階用法 3 - Intercepting Routes
系列文
從 React 學 Next.js:不只要會用,還要真的懂26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言