昨天我們快速回顧了 React 的 Router,也看了 Page Router 和 App Router 的 Router 定義方式,了解到在 Next.js 中,不論是使用 Page Router 還是使用 App Router 都是使用檔案系統的方式定義 Router。 今天讓我們再更深入地再認識 Page Router 和 App Router 多一點。
Page Router 是 Next.js 13 版本之前用來建立路由的主要方式,有別於以往 react-router-dom 需要透過寫 config 檔案來定義路由,Page Router 是以檔案系統來定義路由。簡單來說,也就是透過創建資料夾與檔案, Next.js 就會自動將它們解析成對應的路由。
在新的 Next.js 版本中,Next.js 官方雖然新增了新的 App Router,但是一樣可以使用 Page Router。不過官方還是建議把大家使用 App Router,因為這樣才可以使用 App Router 才有支援的新功能。
在開始看一些細節用法前,我們先來觀察一下當我們建立一個使用 Page Router 的專案時,會出現的兩個基本的檔案用途是什麼?
∙ _app 檔案的用途
import "@/styles/globals.css";
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
_app
檔案是 Next.js 在 Page Router 下的整個應用的「入口元件」,所有透過檔案系統(pages/ 資料夾下)所建立的路由,都會經由這個檔案進行渲染。
如果有需要設定全域樣式、狀態管理、共用 Layout 或頁面切換邏輯等,都需要在這個檔案中處理。
可以嘗試透過印出 Component 和 PageProps 這兩個 props 來更理解 _app
這個檔案的用途。
這裡在 pages 資料夾底下準備一個有以下這個內容的 index 檔案
export function getStaticProps() {
return {
props: {
title: "Home",
},
};
}
function HomePage({ title }: { title: string }) {
return (
<div>
<h1>{title} Page</h1>
</div>
);
}
export default HomePage;
當進入/這個網址的時候,可以看到會印出以下內容
如果切換到其他頁,則會印出那一個頁面相應的 component。如果沒有在頁面單位的元件中,帶上 props 的話,則會是空的物件。
把從 props 取得的 Component 和 PageProps 結合一起這樣使用,也就能讓相對應的頁面被渲染出來。
return <Component {...pageProps} />;
∙ _app
檔案的執行階段
接著我們再來看看 _app
在 Next.js 的不同階段中是怎麼被執行的。
當一個專案被跑起來時,會經歷兩個階段,分別是:
Next.js 會在 build time 先把檔案都掃描過一次,去分析每個頁面要使用哪種渲染模式(如 SSG 或 SSR),等到確認是要使用 SSR 還是 SSG 後,才會進一步決定是否要在這個階段執行 _app 檔案。如果確認是 SSR 就不會先在這個階段執行 _app 檔案,但若是 SSG 則是會在這個階段就執行 _app 檔案來做預渲染,把完整的 HTML 渲染出來。
在 runtime 時,Next.js 則是會真的去執行 _app 檔案來將整個完整的頁面渲染出來。執行的地點會分為 server 端和 client 端,首次進入頁面時,若被判斷是 SSR 的渲染模式,會先在 server 端執行 _app 檔案,組合頁面元件並產出完整的 HTML 回傳給瀏覽器;如果是 SSG 的渲染模式,則會使用 build time 產生好的 HTML。雖然在 SSG 模式下,已經有 build time 預渲染的 HTML,但是在 client 端上還是會再次執行 _app 檔案,因為必須進行 Hydration 的動作,將純靜態的 HTML 與 React App 綁定起來,使頁面能有可以操作的功能。
此外,在透過 Link 或是 router.push() 切換頁面時,Next.js 不會向伺服器發出 HTML 的 request,而是會在 client 端再次執行 _app 檔案,將畫面呈現出來,提供使用者 SPA 的體驗。
∙ _document
檔案的用途
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<body className="antialiased">
<Main />
<NextScript />
</body>
</Html>
);
}
_document
檔案的用途主要是用來自訂完整的 HTML 結構,也就是說主要用途是用來設定 、 和 標籤的部分。如果有 SEO 的需求,需要自定義 meta 標籤,就可以將相關內容設定到這個檔案中。在這個檔案中,一定需要 、、 和 這幾個部分,才能將畫面正確渲染出來。
雖然這個檔案在建立 Page Router 的 Next.js 專案時,預設就會出現這個檔案,但是這個檔案其實不是必要的檔案,如果沒有額外在 pages 資料夾底下建立 _document 檔案,那就會使用 Next.js 框架內預設的 _document 檔案。
這裡也整理出幾個需要客製定義一個 _document
的情境,分別是以下這些:
<html lang="zh-Hant">
<head>
與 <body>
中元素的載入順序或結構∙ _document
檔案的執行階段
除了了解 _document
檔案的用途外,也來看一下 _document
這個檔案的執行階段。
_document
存在的意義就如同一個完整頁面的 HTML 的骨架一樣,不論內容如何變動,這個骨架都不會在渲染畫面時改變,也是因為如此 _document 這個檔案只會在 server 端上執行,用來建立頁面的基本骨架,並不會參與任何 client-side 的互動或動態渲染過程。
在 SSG 的模式下,_document 會先在 build time 的時候就被執行,因為需要預先產出完整的 HTML 檔案(包含 <html>, <head>, <body>
等);在 SSR 模式下,則會在首次請求 SSR 頁面時,也就是在瀏覽器上發出 HTML request 的時候,才會執行 _document 這個檔案,來產出完整的 HTML 結構。無論是 SSG 還是 SSR,_document
的執行都僅發生在 Server 端的初始 HTML 回應階段。透過 Link 或是 router.push() 切換頁面時,就不會再次執行 _document 檔案。
App Router 是 Next.js 從 13 開始新增的路由系統,與傳統的 Page Router 一樣是以檔案系統為基礎定義路由,但主要使用 app 資料夾,而不是 pages 資料夾。
相較於 Page Router,App Router 提供了更多的功能,包含支援巢狀的 Layout 設定,讓畫面架構更易於拆分與維護。除此之外,只有在 App Router 模式下,才支援 React Server Components(RSC),並且預設所有元件都是 Server Component,只有加上 "use client" 關鍵字的元件才會是部分內容需要於 Client 端才可以執行的 Client Component。
前面看過了 Page Router 的基本入口檔案,我們也來看看 App Router 一開始建立專案後,預設的基礎檔案內容。
有別於 Page Router 有一個定義 HTML 標籤、meta 標籤,以及加入第三方 script 的 _document
檔案,以及做完整個應用入口的 _app
檔案,App Router 就相對來得單純,如果要設定 HTML 標籤等內容的話,只需要在 app 資料夾下的第一層透過 Layout 檔案來設定就可以了,也不需要另外用 _app
當作入口檔案來使用,只要直接在 apps 資料夾下建立以巢狀方式處理每一層的畫面架構與共用邏輯 layout 檔案和 page 檔案就好。比起 Page Router,App Router 的用法變得更靈活,也更直覺,甚至提供了許多 Page Router 沒有的功能,這部分我們在後面也會再來仔細看看。
以上就是今天初探 Next.js Page Router 和 App Router 的內容。
在今天的內容中,我們從 Page Router 的 _app
與 _document
檔案,並且了解了傳統 Next.js 的基礎資料夾架構。也看了解到 App Router 能透過 layout 的巢狀設計讓開發者能更靈活地管理畫面結構。雖然以篇幅來說,今天 Page Router 的內容比較多,但以功能來說,App Router 其實支援比較多的功能。所以接下來的幾天,除了會繼續從 Router 常見的相關的功能看 Page Router 和 App Router 下的使用的差異,也會來看幾個在 App Router 才有支援的功能。