2022 年 5 月 23 日 星期一
版面配置 RFC
由這篇 RFC(意見徵求稿)概述了 Next.js 自 2016 年推出以來最大的更新
- 巢狀版面配置: 使用巢狀路由建構複雜的應用程式。
- 為伺服器元件設計: 針對子樹導航進行最佳化。
- 改良的資料獲取: 在版面配置中獲取資料,同時避免瀑布流。
- 使用 React 18 功能: 串流、轉場效果和 Suspense。
- 用戶端和伺服器端路由: 以伺服器為中心的路由,具有 類似 SPA 的行為。
- 100% 可逐步採用:沒有破壞性變更,因此您可以逐步採用。
- 進階路由模式:平行路由、攔截路由等等。
新的 Next.js 路由器將建立在最近發布的 React 18 功能之上。我們計劃引入預設值和慣例,讓您可以輕鬆採用這些新功能,並充分利用它們帶來的優勢。
這篇 RFC 的工作正在進行中,我們將在新功能可用時發布公告。若要提供意見回饋,請加入 Github Discussions 上的討論。
目錄
- 動機
- 術語
- 目前的路由運作方式
app
目錄- 定義路由
- 版面配置
- 頁面
- React 伺服器元件
- 資料獲取
- 路由群組(新增)
- 以伺服器為中心的路由(新增)
- 即時載入狀態(新增)
- 錯誤處理(新增)
- 範本(新增)
- 進階路由模式(新增)
- 結論
動機
我們一直在從 GitHub、Discord、Reddit 和我們的開發者調查中收集社群回饋,了解 Next.js 中目前路由的限制。我們發現
- 建立版面配置的開發者體驗可以改進。應該可以輕鬆建立可巢狀、跨路由共用且在導航時保留其狀態的版面配置。
- 許多 Next.js 應用程式是儀表板或控制台,這些應用程式將受益於更進階的路由解決方案。
雖然目前的路由系統自 Next.js 推出以來一直運作良好,但我們希望讓開發人員更容易建構效能更高、功能更豐富的 Web 應用程式。
作為框架維護者,我們也希望建構一個向後相容且符合 React 未來發展方向的路由系統。
注意: 某些路由慣例的靈感來自 Meta 的基於 Relay 的路由器(Server Components 的某些功能最初在此開發),以及用戶端路由器(如 React Router 和 Ember.js)。
layout.js
檔案慣例的靈感來自 SvelteKit 中的工作。我們也想感謝 Cassidy 開啟了關於 版面配置的早期 RFC。
術語
這篇 RFC 介紹了新的路由慣例和語法。術語基於 React 和標準 Web 平台術語。在整篇 RFC 中,您會看到這些術語連結回下方的定義。
- 樹狀結構: 一種視覺化階層式結構的慣例。例如,具有父元件和子元件的元件樹狀結構、資料夾結構等等。
- 子樹狀結構:樹狀結構的一部分,從根(第一個)開始,到葉(最後一個)結束。

- URL 路徑: URL 中網域之後的部分。
- URL 段: URL 路徑中以斜線分隔的部分。

目前的路由運作方式
目前,Next.js 使用檔案系統將 Pages 目錄中的個別資料夾和檔案對應到可透過 URL 存取的路由。每個頁面檔案都會匯出一個 React 元件,並具有基於其檔案名稱的關聯路由。例如

- 動態路由: Next.js 支援動態路由(包括全部捕獲變體),使用
[param].js
、[...param].js
和[[...param]].js
慣例。 - 版面配置: Next.js 提供對簡單的基於元件的版面配置、使用元件屬性模式的每頁版面配置,以及使用自訂應用程式的單一全域版面配置的支援。
- 資料獲取: Next.js 提供資料獲取方法(
getStaticProps
、getServerSideProps
),可用於頁面(路由)層級。這些方法用於判斷頁面應該是靜態產生(getStaticProps
)還是伺服器端渲染(getServerSideProps
)。此外,您可以使用增量靜態再生 (ISR) 在網站建置後建立或更新靜態頁面。 - 渲染: Next.js 提供三種渲染選項:靜態產生、伺服器端渲染 和 用戶端渲染。預設情況下,頁面是靜態產生的,除非它們有封鎖資料獲取需求(
getServerSideProps
)。
介紹 app
目錄
為了確保這些新改進可以逐步採用並避免破壞性變更,我們建議使用一個名為 app
的新目錄。

app
目錄將與 pages
目錄並行運作。您可以逐步將應用程式的部分移至新的 app
目錄,以利用新功能。為了向後相容性,pages
目錄的行為將保持不變並繼續受到支援。
定義路由
您可以使用 app
內部的資料夾階層來定義路由。路由是巢狀資料夾的單一路徑,遵循從根資料夾到最終葉資料夾的階層。

例如,您可以透過在 app
目錄中巢狀兩個新資料夾來新增 /dashboard/settings
路由。
注意
- 使用此系統,您將使用資料夾來定義路由,並使用檔案來定義 UI(使用新的檔案慣例,例如
layout.js
、page.js
,以及 RFC 第二部分的loading.js
)。- 這可讓您將自己的專案檔案(UI 元件、測試檔案、故事等)共置在
app
目錄中。目前,這僅能透過 pageExtensions 設定完成。
路由段
子樹狀結構中的每個資料夾都代表一個 路由段。每個路由段都會對應到 URL 路徑 中的對應 段。

例如,/dashboard/settings
路由由 3 個段組成
/
根段dashboard
段settings
段
注意:選擇 路由段 這個名稱是為了與圍繞 URL 路徑 的現有術語相符。
版面配置
新的檔案慣例: layout.js
到目前為止,我們已經使用資料夾來定義應用程式的路由。但空資料夾本身不會執行任何操作。讓我們討論如何使用新的檔案慣例來定義將為這些路由渲染的 UI。
版面配置是在 子樹狀結構中路由段之間共用的 UI。當使用者在同層級段之間導航時,版面配置不會影響 URL 路徑,也不會重新渲染(React 狀態會被保留)。
版面配置可以透過從 layout.js
檔案預設匯出 React 元件來定義。該元件應接受 children
prop,該 prop 將填充版面配置正在包裝的段。
版面配置有 2 種型別
- 根版面配置: 適用於所有路由
- 常規版面配置: 適用於特定路由
您可以將兩個或多個版面配置巢狀在一起,以形成 巢狀版面配置。
根版面配置
您可以透過在 app
資料夾內新增 layout.js
檔案來建立將應用於應用程式所有路由的根版面配置。

注意
- 根版面配置取代了對 自訂 App (
_app.js
) 和 自訂 Document (_document.js
) 的需求,因為它適用於所有路由。- 您將能夠使用根版面配置來自訂初始文件外殼(例如
<html>
和<body>
標籤)。- 您將能夠在根版面配置(和其他版面配置)中獲取資料。
常規版面配置
您也可以透過在特定資料夾內新增 layout.js
檔案來建立僅適用於應用程式一部分的版面配置。

例如,您可以在 dashboard
資料夾內建立 layout.js
檔案,該檔案將僅適用於 dashboard
內部的路由段。
巢狀版面配置
版面配置預設為 巢狀。

例如,如果我們要結合上述兩個版面配置。根版面配置 (app/layout.js
) 將應用於 dashboard 版面配置,而 dashboard 版面配置也將應用於 dashboard/*
內部的所有路由段。

頁面
新的檔案慣例: page.js
頁面是路由段獨有的 UI。您可以透過在資料夾內新增 page.js
檔案來建立頁面。

例如,若要為 /dashboard/*
路由建立頁面,您可以在每個資料夾內新增 page.js
檔案。當使用者造訪 /dashboard/settings
時,Next.js 將渲染 settings
資料夾的 page.js
檔案,並將其包裝在子樹狀結構中更上層的任何版面配置中。

您可以直接在 dashboard 資料夾內建立 page.js
檔案,以符合 /dashboard
路由。dashboard 版面配置也將應用於此頁面

此路由由 2 個段組成
/
根段dashboard
段
注意
- 為了使路由有效,它需要在其葉段中包含頁面。如果沒有,路由將拋出錯誤。
版面配置和頁面行為
- 檔案副檔名
js|jsx|ts|tsx
可用於頁面和版面配置。 - 頁面元件是
page.js
的預設匯出。 - 版面配置元件是
layout.js
的預設匯出。 - 版面配置元件必須接受
children
prop。
當渲染版面配置元件時,children
prop 將填充子版面配置(如果子樹狀結構中更下方存在)或頁面。
將其視覺化為版面配置樹狀結構可能會更容易,其中父版面配置將選擇最近的子版面配置,直到到達頁面。
範例

// Root layout
// - Applies to all routes
export default function RootLayout({ children }) {
return (
<html>
<body>
<Header />
{children}
<Footer />
</body>
</html>
);
}
// Regular layout
// - Applies to route segments in app/dashboard/*
export default function DashboardLayout({ children }) {
return (
<>
<DashboardSidebar />
{children}
</>
);
}
// Page Component
// - The UI for the `app/dashboard/analytics` segment
// - Matches the `acme.com/dashboard/analytics` URL path
export default function AnalyticsPage() {
return <main>...</main>;
}
上述版面配置和頁面的組合將渲染以下元件階層
<RootLayout>
<Header />
<DashboardLayout>
<DashboardSidebar />
<AnalyticsPage>
<main>...</main>
</AnalyticsPage>
</DashboardLayout>
<Footer />
</RootLayout>
React 伺服器元件
注意: React 引入了新的元件型別:伺服器、用戶端(傳統 React 元件)和共用。若要深入了解這些新類型,我們建議您閱讀 React 伺服器元件 RFC。
透過這篇 RFC,您可以開始使用 React 功能,並逐步將 React 伺服器元件採用到您的 Next.js 應用程式中。
新路由系統的內部機制也將利用最近發布的 React 功能,例如串流、Suspense 和轉場效果。這些是 React 伺服器元件的建構組塊。
伺服器元件作為預設值
pages
和 app
目錄之間最大的變更之一是,預設情況下,app
內部的檔案將在伺服器上渲染為 React 伺服器元件。
這將讓您在從 pages
遷移到 app
時自動採用 React 伺服器元件。
注意: 伺服器元件可以在
app
資料夾或您自己的資料夾中使用,但為了向後相容性,不能在pages
目錄中使用。
用戶端和伺服器元件慣例
app
資料夾將支援伺服器、用戶端和共用元件,並且您將能夠在樹狀結構中交錯這些元件。

關於定義用戶端元件和伺服器元件的慣例究竟是什麼,目前正在進行討論。我們將遵循此討論的結果。
- 目前,伺服器元件可以透過將
.server.js
附加到檔案名稱來定義。例如layout.server.js
- 用戶端元件可以透過將
.client.js
附加到檔案名稱來定義。例如page.client.js
。 .js
檔案被視為共用元件。由於它們可以在伺服器和用戶端上渲染,因此它們需要遵守每個上下文的限制。
注意
- 用戶端和伺服器元件具有限制,需要遵守這些限制。在決定使用用戶端元件或伺服器元件時,我們建議使用伺服器元件(預設值),直到您需要使用用戶端元件為止。
Hook
我們將新增用戶端和伺服器元件 Hook,讓您可以存取標頭物件、Cookie、路徑名稱、搜尋參數等等。未來,我們將提供包含更多資訊的文件。
渲染環境
您可以使用用戶端和伺服器元件慣例來精細控制哪些元件將位於用戶端 JavaScript 套件中。
預設情況下,app
中的路由將使用靜態產生,當路由段使用需要請求上下文的伺服器端 Hook 時,將切換到動態渲染。
在路由中交錯用戶端和伺服器元件
在 React 中,對於在用戶端元件內部匯入伺服器元件存在限制,因為伺服器元件可能具有僅限伺服器的程式碼(例如,資料庫或檔案系統公用程式)。
例如,匯入伺服器元件將無法運作
import ServerComponent from './ServerComponent.js';
export default function ClientComponent() {
return (
<>
<ServerComponent />
</>
);
}
但是,伺服器元件可以作為用戶端元件的子元件傳遞。您可以透過將它們包裝在另一個伺服器元件中來實現。
export default function ClientComponent({ children }) {
return (
<>
<h1>Client Component</h1>
{children}
</>
);
}
// ServerComponent.js
export default function ServerComponent() {
return (
<>
<h1>Server Component</h1>
</>
);
}
// page.js
// It's possible to import Client and Server components inside Server Components
// because this component is rendered on the server
import ClientComponent from "./ClientComponent.js";
import ServerComponent from "./ServerComponent.js";
export default function ServerComponentPage() {
return (
<>
<ClientComponent>
<ServerComponent />
</ClientComponent>
</>
);
}
透過此模式,React 會知道它需要在伺服器上渲染 ServerComponent
,然後將結果(不包含任何僅限於伺服器的程式碼)傳送到用戶端。從用戶端組件的角度來看,其子組件將已完成渲染。
在版面配置中,此模式會應用於 children
prop,因此您不必建立額外的包裝組件。
例如,ClientLayout
組件將接受 ServerPage
組件作為其子組件
// The Dashboard Layout is a Client Component
export default function ClientLayout({ children }) {
// Can use useState / useEffect here
return (
<>
<h1>Layout</h1>
{children}
</>
);
}
// The Page is a Server Component that will be passed to Dashboard Layout
// app/dashboard/settings/page.js
export default function ServerPage() {
return (
<>
<h1>Page</h1>
</>
);
}
注意: 這種組合樣式是在用戶端組件內渲染伺服器組件的重要模式。它確立了學習一種模式的優先順序,也是我們決定使用
children
prop 的原因之一。
資料fetch
在路由中的多個區段內 fetch 資料將成為可能。這與 pages
目錄不同,在 pages
目錄中,資料 fetch 僅限於頁面層級。
版面配置中的資料fetch
您可以使用 Next.js 資料 fetch 方法 getStaticProps
或 getServerSideProps
,在 layout.js
檔案中 fetch 資料。
例如,部落格版面配置可以使用 getStaticProps
從 CMS fetch 分類,這些分類可用於填充側邊欄組件
export async function getStaticProps() {
const categories = await getCategoriesFromCMS();
return {
props: { categories },
};
}
export default function BlogLayout({ categories, children }) {
return (
<>
<BlogSidebar categories={categories} />
{children}
</>
);
}
路由中的多個資料fetch 方法
您也可以在路由的多個區段中 fetch 資料。例如,fetch 資料的 layout
也可以包裝 fetch 其自身資料的 page
。
以上方的部落格範例為例,單一文章頁面可以使用 getStaticProps
和 getStaticPaths
從 CMS fetch 文章資料
export async function getStaticPaths() {
const posts = await getPostSlugsFromCMS();
return {
paths: posts.map((post) => ({
params: { slug: post.slug },
})),
};
}
export async function getStaticProps({ params }) {
const post = await getPostFromCMS(params.slug);
return {
props: { post },
};
}
export default function BlogPostPage({ post }) {
return <Post post={post} />;
}
由於 app/blog/layout.js
和 app/blog/[slug]/page.js
都使用 getStaticProps
,Next.js 將在建置時以React 伺服器組件靜態產生整個 /blog/[slug]
路由 - 減少用戶端 JavaScript 並加快 hydration。
靜態產生的路由進一步改善了這一點,因為用戶端導航重複使用快取(伺服器組件資料)並且不會重新計算工作,從而減少 CPU 時間,因為您正在渲染伺服器組件的快照。
行為和優先順序
Next.js 資料 Fetch 方法 (getServerSideProps
和 getStaticProps
) 只能在 app
資料夾中的伺服器組件中使用。單一路線中跨區段的不同資料 fetch 方法會相互影響。
在一個區段中使用 getServerSideProps
將會影響其他區段中的 getStaticProps
。由於請求已經必須前往伺服器以處理 getServerSideProps
區段,因此伺服器也將渲染任何 getStaticProps
區段。它將重複使用在建置時 fetch 的 props,因此資料仍將是靜態的,渲染會在每次請求時根據 next build
期間產生的 props 按需發生。
在一個區段中使用具有 revalidate (ISR) 的 getStaticProps
將會影響其他區段中具有 revalidate
的 getStaticProps
。如果一個路由中有兩個重新驗證週期,則較短的重新驗證將優先。
注意: 未來可能會對此進行最佳化,以允許在路由中實現完整的資料 fetch 細微度。
使用 React 伺服器組件進行資料fetch
伺服器端路由、React 伺服器組件、Suspense 和串流的組合,對於 Next.js 中的資料 fetch 和渲染有一些影響
平行資料fetch
Next.js 將積極主動地平行啟動資料 fetch,以盡量減少瀑布效應。例如,如果資料 fetch 是循序的,則路由中的每個巢狀區段都必須等到前一個區段完成後才能開始 fetch 資料。然而,透過平行 fetch,每個區段都可以同時積極主動地開始資料 fetch。

由於渲染可能取決於 Context,因此每個區段的渲染將在其資料 fetch 完成且其父區段完成渲染後開始。
未來,透過 Suspense,即使資料尚未完全載入,渲染也可以立即開始。如果在資料可用之前讀取資料,則會觸發 Suspense。React 將在請求完成之前樂觀地開始渲染伺服器組件,並在請求解析時插入結果。
部分 Fetch 和渲染
在同層級路由區段之間導航時,Next.js 將僅從該區段向下 fetch 和渲染。它不需要重新 fetch 或重新渲染上方的任何內容。這表示在共用版面配置的頁面中,當使用者在同層級頁面之間導航時,版面配置將會保留,而 Next.js 將僅從該區段向下 fetch 和渲染。
這對於 React 伺服器組件特別有用,因為否則每次導航都會導致整個頁面在伺服器上重新渲染,而不是僅在伺服器上渲染頁面的變更部分。這減少了資料傳輸量和執行時間,從而提高了效能。
例如,如果使用者在 /analytics
和 /settings
頁面之間導航,React 將重新渲染頁面區段,但保留版面配置

注意: 將有可能強制重新 fetch 樹狀結構中較高層級的資料。我們仍在討論這將如何呈現的細節,並將更新 RFC。
路由群組
app
資料夾的階層結構直接對應到 URL 路徑。但是,可以透過建立路由群組來打破此模式。路由群組可用於
- 組織路由,而不會影響 URL 結構。
- 將路由區段從版面配置中排除。
- 透過分割應用程式來建立多個根版面配置。
慣例
路由群組可以透過將資料夾名稱包在括號中來建立:(資料夾名稱)
注意: 路由群組的命名僅用於組織目的,因為它們不會影響 URL 路徑。
範例:將路由從版面配置中排除
若要將路由從版面配置中排除,請建立新的路由群組(例如 (shop)
),然後將共用相同版面配置的路由移至群組中(例如 account
和 cart
)。群組外部的路由將不會共用版面配置(例如 checkout
)。
之前

之後

範例:組織路由,而不會影響 URL 路徑
同樣地,若要組織路由,請建立群組以將相關路由放在一起。括號中的資料夾將從 URL 中省略(例如 (marketing)
或 (shop)
)。

範例:建立多個根版面配置
若要建立多個根版面配置,請在 app
目錄的最上層建立兩個或多個路由群組。這對於將應用程式劃分為具有完全不同 UI 或體驗的區段非常有用。每個根版面配置的 <html>
、<body>
和 <head>
標籤可以分別自訂。

以伺服器為中心的路由
目前,Next.js 使用用戶端路由。在初始載入之後和後續導航時,會向伺服器發出請求,以取得新頁面的資源。這包括每個組件的 JavaScript(包括僅在特定條件下顯示的組件)及其 props(來自 getServerSideProps
或 getStaticProps
的 JSON 資料)。從伺服器載入 JavaScript 和資料後,React 會在用戶端渲染組件。
在這個新模型中,Next.js 將使用以伺服器為中心的路由,同時保持用戶端轉換。這與在伺服器上評估的 伺服器組件 一致。
在導航時,會 fetch 資料,而 React 會在伺服器端渲染組件。來自伺服器的輸出是 React 用戶端更新 DOM 的特殊指示(不是 HTML 或 JSON)。這些指示包含已渲染伺服器組件的結果,這表示該組件的 JavaScript 不必載入到瀏覽器中即可渲染結果。
這與目前的用戶端組件預設值形成對比,後者需要將組件 JavaScript 傳送到瀏覽器,以便在用戶端渲染。
使用 React 伺服器組件的以伺服器為中心的路由的一些優點包括
- 路由使用與伺服器組件相同的請求(不會發出額外的伺服器請求)。
- 伺服器端完成的工作較少,因為在路由之間導航僅 fetch 和渲染變更的區段。
- 當用戶端導航時,如果未使用新的用戶端組件,則不會在瀏覽器中載入額外的 JavaScript。
- 路由器利用新的串流協定,以便可以在所有資料載入之前開始渲染。
當使用者在應用程式中導航時,路由器會將 React 伺服器組件payload 的結果儲存在記憶體中的用戶端快取中。快取依路由區段分割,這允許在任何層級進行失效,並確保跨並行渲染的一致性。這表示在某些情況下,可以重複使用先前 fetch 的區段的快取。
注意
- 靜態產生和伺服器端快取可用於最佳化資料 fetch。
- 以上資訊描述了後續導航的行為。初始載入是不同的程序,涉及伺服器端渲染以產生 HTML。
- 雖然用戶端路由在 Next.js 中運作良好,但當潛在路由數量很大時,它的擴充性會很差,因為用戶端必須下載路由地圖。
- 總體而言,透過使用 React 伺服器組件,用戶端導航速度更快,因為我們在瀏覽器中載入和渲染的組件更少。
即時載入狀態
透過伺服器端路由,導航在資料 fetch 和渲染之後發生,因此在 fetch 資料時顯示載入 UI 非常重要,否則應用程式看起來會沒有回應。

新的路由器將使用 Suspense 以實現即時載入狀態和預設骨架畫面。這表示在載入新區段的內容時,可以立即顯示載入 UI。然後,一旦伺服器上的渲染完成,新的內容就會被換入。
在渲染正在進行時
- 到新路由的導航將是立即的。
- 共用的版面配置在新的路由區段載入時仍將保持互動性。
- 導航將是可中斷的 - 這表示使用者可以在一個路由的內容正在載入時,在路由之間導航。
預設載入骨架畫面
Suspense 邊界將透過稱為 loading.js
的新檔案慣例在幕後自動處理。
範例
您將能夠透過在資料夾內新增 loading.js
檔案來建立預設載入骨架畫面。

loading.js
應匯出 React 組件
export default function Loading() {
return <YourSkeleton />
}
// layout.js
export default function Layout({children}) {
return (
<>
<Sidebar />
{children}
</>
)
}
// Output
<>
<Sidebar />
<Suspense fallback={<Loading />}>{children}</Suspense>
</>
這將導致資料夾中的所有區段都包裝在 suspense 邊界中。預設骨架畫面將在首次載入版面配置以及在同層級頁面之間導航時使用。
錯誤處理
錯誤邊界 是 React 組件,可捕捉其子組件樹狀結構中任何位置的 JavaScript 錯誤。
慣例
您將能夠建立錯誤邊界,透過新增 error.js
檔案並預設匯出 React 組件,來捕捉子樹狀結構中的錯誤。

如果該子樹狀結構內擲回錯誤,則會顯示該組件作為後備。此組件可用於記錄錯誤、顯示有關錯誤的有用資訊,以及嘗試從錯誤中恢復的功能。
由於區段和版面配置的巢狀性質,建立錯誤邊界可讓您將錯誤隔離到 UI 的這些部分。在發生錯誤期間,邊界上方的版面配置將保持互動性,並且它們的狀態將被保留。
export default function Error({ error, reset }) {
return (
<>
An error occurred: {error.message}
<button onClick={() => reset()}>Try again</button>
</>
);
}
// layout.js
export default function Layout({children}) {
return (
<>
<Sidebar />
{children}
</>
)
}
// Output
<>
<Sidebar />
<ErrorBoundary fallback={<Error />}>{children}</ErrorBoundary>
</>
注意
- 與
error.js
位於相同區段的layout.js
檔案中的錯誤將不會被捕捉,因為自動錯誤邊界會包裝版面配置的子組件,而不是版面配置本身。
樣板
樣板與版面配置類似,因為它們包裝每個子版面配置或頁面。
與跨路由持久存在並保持狀態的版面配置不同,樣板會為每個子組件建立新的執行個體。這表示當使用者在共用樣板的路由區段之間導航時,會掛載組件的新執行個體。
注意: 除非您有使用樣板的特定原因,否則我們建議使用版面配置。
慣例
樣板可以透過從 template.js
檔案匯出預設 React 組件來定義。組件應接受 children
prop,該 prop 將填充巢狀區段。
範例

export default function Template({ children }) {
return <Container>{children}</Container>;
}
具有版面配置和樣板的路由區段的渲染輸出將如下所示
<Layout>
{/* Note that the template is given a unique key. */}
<Template key={routeParam}>{children}</Template>
</Layout>
行為
在某些情況下,您可能需要掛載和卸載共用 UI,而樣板將是更合適的選項。例如
- 使用 CSS 或動畫庫的進入/退出動畫
- 依賴
useEffect
(例如記錄頁面瀏覽次數)和useState
(例如每個頁面的意見回饋表單)的功能 - 變更預設框架行為。例如,版面配置內的 suspense 邊界僅在首次載入版面配置時顯示後備,而在切換頁面時則不顯示。對於樣板,每次導航都會顯示後備。
例如,考慮巢狀版面配置的設計,其中帶邊框的容器應包裝在每個子頁面周圍。
您可以將容器放在父版面配置內 (shop/layout.js
)
export default function Layout({ children }) {
return <div className="container">{children}</div>;
}
// shop/page.js
export default function Page() {
return <div>...</div>;
}
// shop/categories/layout.js
export default function CategoryLayout({ children }) {
return <div>{children}</div>;
}
但是,當切換頁面時,任何進入/退出動畫都不會播放,因為共用的父版面配置不會重新渲染。
您可以將容器放在每個巢狀版面配置或頁面中
export default function Layout({ children }) {
return <div>{children}</div>;
}
// shop/page.js
export default function Page() {
return <div className="container">...</div>;
}
// shop/categories/layout.js
export default function CategoryLayout({ children }) {
return <div className="container">{children}</div>;
}
但是,您必須手動將其放在每個巢狀版面配置或頁面中,這在更複雜的應用程式中可能很繁瑣且容易出錯。
透過此慣例,您可以跨路由共用樣板,這些樣板會在導航時建立新的執行個體。這表示 DOM 元素將被重新建立,狀態將不會被保留,並且效果將被重新同步。
進階路由模式
我們計劃推出慣例來涵蓋邊緣案例,並讓您實作更進階的路由模式。以下是我們一直在積極思考的一些範例
攔截路由
有時,從其他路由內攔截路由區段可能很有用。在導航時,URL 將照常更新,但攔截的區段將顯示在目前路由的版面配置中。
範例
之前: 按一下影像會導致進入具有自身版面配置的新路由。

之後: 透過攔截路由,現在按一下影像會將區段載入到目前路由的版面配置中。例如,作為模式視窗。

若要從 /[username]
區段內攔截 /photo/[id]
路由,請在 /[username]
資料夾內建立重複的 /photo/[id]
資料夾,並在前面加上 (..)
慣例。

慣例
(..)
- 將比對高一層級的路由區段(父目錄的同層級)。類似於相對路徑中的../
。(..)(..)
- 將比對高兩層級的路由區段。類似於相對路徑中的../../
。(...)
- 將比對根目錄中的路由區段。
注意: 重新整理或共用頁面將載入具有其預設版面配置的路由。
動態平行路由
有時,在同一個視圖中顯示兩個或多個可以獨立導航的葉區段 (page.js
) 可能很有用。
以同一個儀表板內的兩個或多個標籤群組為例。導航一個標籤群組不應影響另一個標籤群組。當向後和向前導航時,標籤的組合也應正確還原。

慣例
預設情況下,版面配置接受名為 children
的 prop,其中將包含巢狀版面配置或頁面。您可以透過建立具名的「插槽」(包含 @
前綴的資料夾)並在其內巢狀區段來重新命名該 prop。

在此變更之後,版面配置將接收名為 customProp
而不是 children
的 prop。
export default function Layout({ customProp }) {
return <>{customProp}</>;
}
您可以透過在同一層級新增多個具名插槽來建立平行路由。在以下範例中,@views
和 @audience
都作為 props 傳遞到分析版面配置。

您可以使用具名插槽同時顯示葉區段。
export default function Layout({ views, audience }) {
return (
<>
<div>
<ViewsNav />
{views}
</div>
<div>
<AudienceNav />
{audience}
</div>
</>
);
}
當使用者首次導航到 /analytics
時,會顯示每個資料夾(@views
和 @audience
)中的 page.js
區段。
導航到 /analytics/subscribers
時,僅更新 @audience
。同樣地,導航到 /analytics/impressions
時,僅更新 @views
。
向後和向前導航將恢復平行路由的正確組合。
組合攔截路由和平行路由
您可以組合攔截路由和平行路由,以在應用程式中實現特定的路由行為。
範例
例如,在建立模式視窗時,您通常需要注意一些常見的挑戰,例如
- 模式視窗無法透過 URL 存取。
- 重新整理頁面時,模式視窗會關閉。
- 向後導航會前往先前的路由,而不是模式視窗後面的路由。
- 向前導航不會重新開啟模式視窗。
您可能希望模式視窗在開啟時更新 URL,並且向後/向前導航開啟和關閉模式視窗。此外,在共用 URL 時,您可能希望頁面在載入時開啟模式視窗並顯示其後面的內容,或者您可能希望頁面在載入時不顯示模式視窗的內容。
社群媒體網站上的照片是這方面的一個很好的例子。通常,可以從使用者的動態消息或個人資料中,在模式視窗內存取照片。但是在共用照片時,它們會直接顯示在自己的頁面上。
透過使用慣例,我們可以預設將模式視窗行為對應到路由行為。
考慮以下資料夾結構

透過此模式
/photo/[id]
的內容可以透過其自身內容中的 URL 存取。也可以從/[username]
路由內的模式視窗存取。- 使用用戶端導航向後和向前導航應關閉和重新開啟模式視窗。
- 重新整理頁面(伺服器端導航)應將使用者帶到原始
/photo/id
路由,而不是顯示模式視窗。
在 /@modal/(..)photo/[id]/page.js
中,您可以傳回包裝在模式視窗組件中的頁面內容。
export default function PhotoPage() {
const router = useRouter();
return (
<Modal
// the modal should always be shown on page load
isOpen={true}
// closing the modal should take user back to the previous page
onClose={() => router.back()}
>
{/* Page Content */}
</Modal>
);
}
注意: 此解決方案並非在 Next.js 中建立模式視窗的唯一方法,而是旨在展示如何組合慣例以實現更複雜的路由行為。
條件路由
有時候,您可能需要動態資訊,例如資料或上下文,來決定要顯示哪個路由。您可以使用平行路由來有條件地載入一個或另一個路由。
範例
export async function getServerSideProps({ params }) {
const { accountType } = await fetchAccount(params.slug);
return { props: { isUser: accountType === 'user' } };
}
export default function UserOrTeamLayout({ isUser, user, team }) {
return <>{isUser ? user : team}</>;
}
在上面的範例中,您可以根據 slug 返回 user
或 team
路由。這允許您有條件地載入資料,並將子路由與其中一個選項進行匹配。
結論
我們對於 Next.js 中版面配置、路由和 React 18 的未來感到興奮。實作工作已經開始,我們將在功能可用時發布公告。