Next.js App Router vs Pages Router:核心区别和渐进式迁移指南
Next.js 13 引入的 App Router 不是 Pages Router 的替代品——它是一套全新的架构,基于 React Server Components,改变了数据获取、渲染和路由的整个思维方式。迁移不是改个目录名那么简单。
核心区别一表看懂
| 维度 | Pages Router | App Router |
|---|---|---|
| 目录 | pages/ | app/ |
| 路由文件 | pages/about.js | app/about/page.tsx |
| API 路由 | pages/api/users.js | app/api/users/route.ts |
| 布局 | _app.js + 唯一 layout | 嵌套 layout(每个目录可以有) |
| 数据获取 | getServerSideProps / getStaticProps | 组件内直接 async/await |
| 渲染模型 | 客户端组件为主 | 服务端组件为主(RSC) |
| 状态 | 天然客户端 | 服务端组件无状态 |
数据获取:最大的变化
Pages Router 方式
typescript// pages/users.tsx export async function getServerSideProps() { const users = await fetchUsers() return { props: { users } } } export default function UsersPage({ users }) { return <UserList users={users} /> }
每个页面需要单独的 getServerSideProps 或 getStaticProps 函数,数据只能在页面级获取,子组件不能自己获取。
App Router 方式
typescript// app/users/page.tsx export default async function UsersPage() { const users = await fetchUsers() // 直接在组件里 await return <UserList users={users} /> }
不需要特殊的 getXXXProps 函数——组件本身就是 async 函数,直接 await 获取数据。子组件也能自己获取:
typescript// components/user-stats.tsx export default async function UserStats() { const stats = await fetchStats() // 子组件自己获取数据 return <div>活跃用户:{stats.active}</div> }
这是 RSC 的核心优势:组件级数据获取,不再需要在页面顶层把所有数据攒齐再一层层传 props。
布局系统:从全局到嵌套
Pages Router:一个布局管所有页面
typescript// pages/_app.tsx - 全局唯一的布局 export default function App({ Component, pageProps }) { return ( <Layout> <Component {...pageProps} /> </Layout> ) }
所有页面共享同一个 _app.js。想做"某些页面有侧边栏,某些没有"很别扭——要靠条件判断或把布局塞进每个页面。
App Router:嵌套布局,每个目录可以有
shellapp/ layout.tsx # 根布局(必须有 html + body) page.tsx # 首页 dashboard/ layout.tsx # dashboard 专属布局(侧边栏) page.tsx # dashboard 首页 settings/ page.tsx # settings 页(继承 dashboard 布局)
typescript// app/dashboard/layout.tsx export default function DashboardLayout({ children }) { return ( <div className="flex"> <Sidebar /> <main className="flex-1">{children}</main> </div> ) }
/dashboard/settings 会渲染三层布局:根 layout → dashboard layout → settings page。切换 settings 页面时,dashboard layout 不会重新渲染——侧边栏状态保留。
这是 App Router 布局的核心:嵌套且持久化。Pages Router 做不到这一点——每次路由切换都重新挂载整个页面。
Server Components vs Client Components
这是迁移时最容易搞混的概念:
Server Components(默认)
typescript// app/page.tsx - 自动是 Server Component export default async function Page() { const data = await fetch('https://api.example.com/data') return <div>{data.title}</div> }
- 在服务端渲染,不会发送 JS 到客户端
- 可以直接访问数据库、文件系统
- 不能用
useState、useEffect、浏览器 API - 不能绑定事件处理函数(
onClick等)
Client Components(需要显式声明)
typescript'use client' // 这行声明是 Client Component import { useState } from 'react' export default function Counter() { const [count, setCount] = useState(0) return <button onClick={() => setCount(count + 1)}>{count}</button> }
- 在客户端渲染(可以 hydration)
- 可以用所有 React Hooks
- 可以绑定事件、使用浏览器 API
- 不能直接
await异步数据
选择原则
| 需要 | 选择 |
|---|---|
| 获取数据 | Server Component |
| 访问后端资源 | Server Component |
| useState/useEffect | Client Component |
| 事件处理(onClick) | Client Component |
| 浏览器 API(window) | Client Component |
默认用 Server Component,只在需要交互时才用 Client Component。 这和 Pages Router 的思维完全相反——Pages Router 默认全是客户端组件。
API 路由的变化
Pages Router
typescript// pages/api/users.ts import type { NextApiRequest, NextApiResponse } from 'next' export default function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === 'GET') { res.status(200).json({ users: [] }) } }
App Router
typescript// app/api/users/route.ts import { NextResponse } from 'next/server' export async function GET() { const users = await fetchUsers() return NextResponse.json({ users }) } export async function POST(request: Request) { const body = await request.json() const user = await createUser(body) return NextResponse.json(user, { status: 201 }) }
变化:从单个 handler 函数 + if (req.method) 拆成独立的方法函数。每个 HTTP 方法一个导出函数,更清晰。
迁移策略
不要一次性全迁移——两个 Router 可以共存:
1. 渐进式迁移
shellpages/ old-page.tsx # 还在用 Pages Router app/ new-page/ page.tsx # 新页面用 App Router
Next.js 同时支持两种 Router。新页面用 App Router,旧页面保持不动,逐步迁移。
2. 迁移优先级
- API 路由:最简单,改动最小
- 纯展示页面:不需要交互,直接改成 Server Component
- 有交互的页面:拆成 Server + Client 组件混合
3. 常见迁移坑
useRouter不一样了:Pages Router 用next/router,App Router 用next/navigationgetServerSideProps变成组件内 async:不再需要序列化/反序列化 props_app.js里的 Provider:移到app/layout.tsx,但要包在 Client Component 里- 图片组件:
next/imageAPI 有变化,注意fill属性替代了layout="fill"
什么时候还该用 Pages Router
不是所有项目都需要迁移:
- 老项目稳定运行:没有性能问题,不需要 RSC,不必迁移
- 团队不熟悉 RSC:App Router 的思维模式完全不同,迁移成本不只是改代码
- 重度依赖客户端状态:如果你的页面几乎全是客户端交互,RSC 的优势体现不出来
App Router 的核心价值是服务端渲染优先 + 嵌套布局。如果你的项目确实受益于这两点,值得迁移;否则 Pages Router 继续用就好——Next.js 没有废弃它的计划。