WorkBlog

Next.js App Router 遷移教學:從 Pages Router 無痛升級完整指南

楊靜宜
Next.js App Router 遷移教學:從 Pages Router 無痛升級完整指南

自從 Next.js 13 推出 App Router 以來,很多團隊都面臨一個問題:「我們現有的 Pages Router 專案要不要遷移?怎麼遷?」

答案是:要遷,但不急著一次全部遷完。Next.js 官方支援兩種路由器共存,你可以漸進式地遷移,不需要大爆炸式的重構。

我最近幫公司把一個 50+ 頁面的 Pages Router 專案遷到 App Router,踩了不少坑。這篇會把我的經驗整理出來,讓你少走彎路。相關的前端知識,你也可以看看React 19 新功能完整介紹

為什麼要遷移到 App Router

如果你的 Pages Router 專案跑得好好的,為什麼要花時間遷移?幾個實際的理由:

  • Server Components:預設在伺服器端渲染,大幅減少客戶端 JavaScript bundle size
  • 巢狀佈局(Nested Layouts):不再需要在每個頁面都重複 Layout 邏輯,路由切換時共用佈局不會重新渲染
  • 串流渲染(Streaming SSR):配合 Suspense,可以逐步載入頁面,提升使用者體驗
  • Server Actions:在 Server Component 中直接處理表單提交,不需要額外建 API Route
  • 未來的功能更新都以 App Router 為主:Pages Router 進入維護模式,新功能(如 Partial Prerendering)只有 App Router 才有

遷移前的準備工作

動手之前,先做這幾件事:

  1. 升級到 Next.js 最新版:確保你的 Next.js 版本是 14.x 以上。App Router 在 14 之後穩定多了
  2. 盤點現有頁面:列出所有 pages/ 目錄下的檔案,標記哪些是純靜態、哪些用了 getServerSideProps、哪些用了 getStaticProps
  3. 確認第三方套件的相容性:有些套件(特別是依賴 Client-side 狀態的)可能需要升級或替換
  4. 建立 app/ 目錄:Next.js 允許 pages/ 和 app/ 共存,所以你可以直接建立 app/ 目錄開始遷移

路由結構對照與轉換

最基本的差異是檔案命名規則:

Pages Router

pages/
├── index.tsx          → /
├── about.tsx          → /about
├── blog/
│   ├── index.tsx      → /blog
│   └── [slug].tsx     → /blog/:slug
└── _app.tsx           → 全域 Layout

App Router

app/
├── page.tsx           → /
├── layout.tsx         → 全域 Layout
├── about/
│   └── page.tsx       → /about
└── blog/
    ├── page.tsx       → /blog
    └── [slug]/
        └── page.tsx   → /blog/:slug

最大的不同:App Router 裡每個路由都是一個資料夾,裡面放 page.tsx。這個設計讓你可以在同一個路由資料夾裡放 layout.tsxloading.tsxerror.tsx 等特殊檔案。

Layout 與 Template 的設計

App Router 最大的優勢之一就是巢狀佈局。在 Pages Router 裡,你可能這樣做:

// Pages Router: 每個頁面都要手動套 Layout
export default function BlogPage() {
  return (
    <MainLayout>
      <BlogSidebar>
        <article>...</article>
      </BlogSidebar>
    </MainLayout>
  );
}

App Router 中,你只需要在對應的路由資料夾放一個 layout.tsx

// app/blog/layout.tsx
export default function BlogLayout({ children }) {
  return (
    <div className="flex">
      <BlogSidebar />
      <main>{children}</main>
    </div>
  );
}

所有 /blog/* 路由都會自動套用這個佈局,而且切換頁面時 Sidebar 不會重新渲染

注意 layout.tsxtemplate.tsx 的差別:Layout 在路由切換時保持狀態(不重新掛載),Template 每次切換都會重新掛載。大部分情況用 Layout 就對了。

資料獲取方式的改變

這是遷移中改動最大的部分。Pages Router 的三大資料獲取函式在 App Router 中都消失了:

getStaticProps → 直接在 Server Component 中 fetch

// App Router: Server Component (預設)
async function BlogPage({ params }) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`);
  const data = await post.json();
  return <Article data={data} />;
}

getServerSideProps → 加上 cache: 'no-store'

const res = await fetch('https://api.example.com/data', {
  cache: 'no-store' // 等同於 SSR
});

getStaticPaths → generateStaticParams

export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

Client Component vs Server Component

App Router 的所有元件預設都是 Server Component。如果你需要用到 useState、useEffect、事件處理等客戶端功能,需要在檔案頂部加上 'use client'

遷移策略:

  1. 先把整個頁面搬過去,如果用了 hooks,先加上 'use client'
  2. 然後逐步把不需要互動的部分抽成 Server Component
  3. 只把真正需要客戶端功能的部分保持為 Client Component

常見的 Client Component:表單、搜尋框、下拉選單、動態互動元素。
適合 Server Component:文章內容、列表頁、靜態區塊、資料庫查詢。

Metadata 與 SEO 設定遷移

Pages Router 用 next/head 設定 SEO 標籤,App Router 改用 Metadata API

// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
  const post = await getPost(params.slug);
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  };
}

這個 API 比 next/head 更好用,而且支援動態生成、自動去重複、和 Server Component 完美整合。

中介軟體與認證處理

好消息:middleware.ts 的用法在兩個 Router 中是一樣的,不需要改。

但如果你用了自定義的認證邏輯(像是在 getServerSideProps 裡檢查 Session),需要改成:

  • 在 Server Component 中使用 cookies()headers() 讀取認證資訊
  • 或者在 middleware.ts 中統一處理認證邏輯
  • 搭配 next-auth v5 的話,它已經原生支援 App Router

漸進式遷移策略

推薦的遷移順序:

  1. 第一步:建立 app/layout.tsx,把 _app.tsx 的全域設定搬過去
  2. 第二步:從最簡單的靜態頁面開始遷移(About、Contact 等)
  3. 第三步:遷移列表頁和詳細頁(Blog Index → Blog Detail)
  4. 第四步:遷移有複雜互動的頁面(Dashboard、表單等)
  5. 第五步:遷移 API Routes 到 Route Handlers
  6. 第六步:清理 pages/ 目錄中已遷移的檔案

重要提醒:同一個路由不能同時存在於 pages/ 和 app/ 中。如果 pages/about.tsx 和 app/about/page.tsx 同時存在,Next.js 會報錯。

結語

Pages Router 到 App Router 的遷移不是一蹴可幾的事,但也沒有想像中可怕。關鍵是漸進式遷移——不需要一次全部改完,一次改一兩個頁面,保持專案可以正常運行。

如果你的專案還在 Pages Router,我建議至少把新功能開始用 App Router 開發。等團隊熟悉了 Server Component 的開發模式,再回頭慢慢遷移舊頁面。

楊靜宜

前端工程師,CSS 重度愛好者。Tailwind CSS 佈道者。

CSSTailwind前端效能無障礙設計

你可能也喜歡

探索其他領域的精選好文