作為獲得眾多支持的 Web 開發框架,Next.js 在版本 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:透過資料夾和特定檔案名稱進行結構化
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,並根據不同的專案和製作需求靈活運用各自的路由器。
我主要從事標記語言、JavaScript、React 和 Next.js 的前端開發。看到自己參與的網站順利上線時最開心!興趣是彈吉他。喜歡貓咪和烤地瓜🐱🍠
Hiracchi
前端工程師 / 2022年入職