Next.js 在很长一段时间内使用 pages 目录作为路由系统的入口,即 Pages Router。从 v13 开始,Next.js 引入了全新的 App Router,基于 app 目录和 React Server Components 构建,标志着 Next.js 路由和数据获取范式的根本性升级。理解两者的差异,是正确使用 Next.js 和评估老旧项目改造的前提。
Pages Router 回顾
Pages Router 通过文件系统约定(pages/about.tsx → /about)自动生成路由,每个页面是一个 React 组件。它的核心机制包括:
- 静态生成 (SSG):通过
getStaticProps在构建时获取数据。 - 服务端渲染 (SSR):通过
getServerSideProps在每次请求时获取数据。 - 客户端渲染 (CSR):页面中组件可直接使用客户端 Hook(
useState、useEffect等)。 - 全局 App 和 Document:
_app.tsx和_document.tsx用于注入全局样式、脚本等。 - API 路由:
pages/api目录下文件即服务端函数。
这种方式概念简单,学习成本低,但存在一些局限性:数据获取与组件耦合较紧,布局复用不够灵活,且整个页面难以自动实现更细粒度的流式渲染。
App Router 新特性
App Router 使用 app 目录,核心是基于 React Server Components(RSC) 的架构。主要变化如下:
1. 路由定义
使用文件夹和文件命名约定:app/dashboard/page.tsx 映射到 /dashboard,app/dashboard/layout.tsx 为该路由的共享布局。
2. 服务端组件默认
所有组件默认为服务端组件,在服务器上运行,不发送 JavaScript 到客户端。需要交互性时,才用 'use client' 指令标记为客户端组件。
3. 数据获取
不再需要 getStaticProps / getServerSideProps,而是直接在异步服务端组件中使用 async/await 获取数据,天然支持 Streaming。
// app/posts/page.tsx
export default async function PostsPage() {
const posts = await fetch('https://api.example.com/posts'); // 自动缓存
return <PostList posts={posts} />;
}
4. 布局与嵌套
通过 layout.tsx 实现嵌套布局,布局在路由切换时保持状态且不重新渲染,大幅提升导航性能。
5. 流式传输与 Suspense
支持页面级的流式 SSR:使用 loading.tsx 或手动 <Suspense> 边界,实现先渲染外壳、后填充独立数据块的渐进式渲染。
6. 全新的 Server Actions
允许服务端函数直接在客户端组件中调用,简化表单提交等操作,无需手动创建 API 路由。
核心差异对比
| 维度 | Pages Router | App Router |
|------|--------------|------------|
| 组件默认运行环境 | 客户端组件(可用 Hook) | 服务端组件(零客户端 JS) |
| 数据获取方式 | getStaticProps / getServerSideProps | 异步组件 + fetch(自动去重与缓存) |
| 布局 | 手动在 _app.tsx 或页面中处理 | 声明式嵌套布局 layout.tsx,路由切换保留状态 |
| 客户端组件 | 所有组件都可交互 | 需 'use client',仅在需要交互时标记 |
| 流式渲染 | 不支持(页面整体输出) | 通过 <Suspense> 和 loading.tsx 原生支持 |
| API 路由 | pages/api | app/api,同时支持 Route Handlers |
| 元数据 | 通常使用 <Head> 或 next/head | 通过 Metadata API 或 generateMetadata |
| 学习曲线 | 低,概念简单 | 中高,需理解 Server/Client Components 边界 |
迁移与选择建议
- 新项目建议直接使用 App Router。Next.js 团队已将主要开发精力放在 App Router 上,RSC 架构是目前前端渲染的最优方案之一。
- 旧项目稳定运行不必强行迁移。Pages Router 会长期维护,两者可以共存(
pages与app目录同时存在时,App Router 优先匹配)。 - 渐进式迁移:可从部分路由开始,例如将数据密集的列表页迁移到 App Router 获得流式加载优势,同时保留简单宣传页在 Pages Router。
实用代码示例对比
Pages Router 博客列表页:
// pages/posts.tsx
export async function getStaticProps() {
const res = await fetch('https://api.example.com/posts');
return { props: { posts: await res.json() } };
}
export default function Posts({ posts }) {
return <PostList posts={posts} />;
}
App Router 对应实现:
// app/posts/page.tsx
export default async function Posts() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 } // 增量静态再生 (ISR) 时间
}).then(res => res.json());
return <PostList posts={posts} />;
}
App Router 版本代码更直接,网络请求与组件天然结合,并且基于 fetch 的缓存策略(revalidate、force-cache)取代了静态生成配置项。
理解这两种路由系统的差异,能够帮助你根据项目需求、团队熟悉程度和性能目标作出合理的技术选择。无论采用哪种方案,Next.js 都能提供坚实的生产级 React 框架支持。