Next.js 是備受眾多開發者青睞的 Web 開發框架,在版本 13 中,新的 App Router 被導入為預設的路由系統。
在同時參與 Pages Router 與 App Router 專案的過程中,相信有不少人會搞混「到底是哪種寫法來著?」我自己就是如此!
特別是從基於檔案結構的路由定義方式開始,到 URL 動態部分的處理方式,再到渲染機制,以及從 API 取得資料等,這些攸關應用程式開發根本的部分,Pages Router 與 App Router 之間存在著不容忽視的差異。
聚焦於「容易搞混」的重點,我將 Pages Router 與 App Router 的主要差異簡單整理成備忘錄,供大家參考!
檔案路由的差異
Pages Router:檔案名稱直接成為路徑,簡潔明瞭
放置在 pages 目錄下的 .js、.jsx、.ts、.tsx 檔案,其檔案名稱會直接作為 URL 路徑,這就是它的簡潔之處。
例如:src/page/about.tsx ⇒ 路由為 /about。
App Router:以資料夾與特定檔名進行結構化
透過在 src/app 底下放置資料夾或檔案,即可設定路由。
不過,若路由所使用的檔案名稱為特定檔名 page,該檔案會被視為對應該路徑的頁面內容。
範例:src/app/about/page.tsx ⇒ 路由為 /about。
App Router 的特色「特殊檔案」
此外,App Router 中除了 page.tsx 以外,還有其他具有特定意義的檔案。
app/
├── page.tsx --> ページの中身。これが一番よく使うファイル
├── route.tsx --> APIの定義(page.jsと共存不可)
├── layout.tsx --> 共通の見た目
├── loading.tsx --> 読み込み中の画面
├── error.tsx --> エラー時の画面
├── global-error.tsx --> グローバルエラー画面
├── templete.tsx --> 共通の見た目(リセットされるレイアウト)
├── default.tsx --> デフォルトの画面
└── not-found.tsx --> notFound関数がスローされたときの画面
讓我印象最深刻的是 not-found.tsx。只要準備好這個頁面,存取不存在的 URL 時就會自動顯示它。沒想到實作起來竟然這麼簡單,令人佩服。
渲染方式的差異
Pages Router 中的元件基本上以客戶端互動為前提,伺服器端渲染主要用於加快初始顯示速度及提升 SEO。useState 與 useEffect 等 React Hook 可在頁面元件及其子元件中自由使用。
另一方面,App Router 中 app/ 內的元件,除非明確指定,否則預設視為伺服器元件。因此 useState 與 useEffect 等 React Hook 無法使用。客戶端的動作與 onClick 等 事件處理函式也無法使用。
因此,若要在 App Router 中改為 CSR,請撰寫 use client。
use client
這是 Server Component 與 Client Component 的邊界宣告。只要宣告了 use client,不僅該元件本身,所有被它匯入的模組也都會被視為 Client Component。
動態路由 [○○]
對於部落格或新聞文章的詳細頁面這類,URL 的一部分會因使用者或內容而改變的「動態 URL」,Next.js 提供的處理機制就是動態路由。
例如,假設有連結分別導向文章 A 與文章 B。
import Link from "next/link";
const linkStyle=`ext-lg font-bold border-b border-primary px-1`
export default function page() {
return (
<div>
<div className="flex gap-6">
<Link href="/post/A" className={linkStyle}>記事Aへのリンク</Link>
<Link href="/post/B" className={linkStyle}>記事Bへのリンク</Link>
</div>
</div>
);
}各連結的跳轉目標
使用動態路由時,只要建立 [slug] 檔案,post/A 與 post/B 就能共用同一個範本。
※ [ ] 中的命名可自由決定,不一定要使用 slug。
App Router 的情況
/app/post/[slug]/page.tsx
export default aysinc function PostPage({ params }: { params: Promise<{ slug: string }>) {
const {slug}= await params;
return (
<div>
<h1>記事: {slug}</h1>
</div>
);
}
使用 params。params.slug 會取得 URL 中 [slug] 的部分。
※ 過去透過 params 取得參數並不需要非同步處理,但從 Next.js 15 起已改為非同步。因此,似乎需要將 props 定義為 Promise 型別。
Pages Router 的情況
/pages/post/[slug].tsx
import { useRouter } from 'next/router';
export default function PostPage() {
const router = useRouter();
const { slug } = router.query;
return (
<div>
<h1>記事: {slug}</h1>
</div>
);
}
使用 useRouter。router.query.slug 會取得 URL 中 [slug] 部分的值。
API 資料取得方法
正如本 Liberogic 專欄所示,microCMS 與 Next.js 等框架搭配起來十分順手。
App Router 的情況
簡單使用 fetch 與 async await 即可。
不過,宣告了 use client 的檔案會變成 CSR,因此無法使用 async await!
async function getPost() {
const res = await fetch(`https://.../api/data/`);
// エラーハンドリング例
if (!res.ok) {
throw new Error(`Failed to fetch post: ${res.status}`);
}
return res.json();
}
被呼叫端
export default function Example() {
const data = await getPost()
// ... data を使って表示 ...
}
Pages Router 的情況
使用 getStaticProps 取得資料,並將取得的資料以 props 傳遞給頁面元件。
export async function getServerSideProps(context) {
// context.req, context.res などにアクセス可能
const res = await fetch(`https://.../api/data/`, {
headers: { Cookie: context.req.headers.cookie } // 例: Cookieを渡す
});
const data = await res.json();
return {
props: {
data,
},
};
}
被呼叫端
export default function Example({ data }) {
// ... data を使って表示 ...
}
理解兩種路由器,善用 Next.js!
我最初接觸 Next.js 的專案是使用 App Router。
之後在另一個專案中接觸 Pages Router 時,我又陷入了「新增頁面要用 index.tsx 還是 page.tsx 來著??」的疑問。
這次的內容只是 Next.js 的一小部分,但藉由重新查閱 App Router 與 Pages Router 的差異,我也把自己的認知整理清楚了。
我個人很喜歡 App Router 的寫法,覺得很容易理解,但也希望能在 Pages Router 的專案中確實應付自如。
理想的狀態是能更深入理解 Next.js,並依照專案或作品的需求,靈活運用各自的 Router!
主要以標記(markup)為核心,並使用 JavaScript、React 與 Next.js 進行前端開發。若我參與的網站能順利上線,我會非常開心!興趣是彈吉他。喜歡貓咪和烤地瓜🐱🍠
Hirattch
前端工程師 / 2022 年入職