Next.js 13+ 引入了全新的 App Router,与传统的 Pages Router 相比有显著差异。App Router 基于 React Server Components 构建,提供了更强大的功能和更好的性能。
主要区别
1. 文件结构
Pages Router:
shellpages/ index.js about.js api/ users.js
App Router:
shellapp/ page.js about/ page.js api/ users/ route.js
2. 布局系统
App Router 的布局系统更强大:
javascript// app/layout.js import './globals.css'; export default function RootLayout({ children }) { return ( <html lang="zh-CN"> <body> <header>全局头部</header> {children} <footer>全局页脚</footer> </body> </html> ); } // app/about/layout.js export default function AboutLayout({ children }) { return ( <div className="about-layout"> <aside>关于我们侧边栏</aside> <main>{children}</main> </div> ); }
3. 数据获取方式
Pages Router:
javascript// pages/index.js export async function getServerSideProps() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); return { props: { data } }; } export default function Home({ data }) { return <div>{data.title}</div>; }
App Router:
javascript// app/page.js async function getData() { const res = await fetch('https://api.example.com/data', { next: { revalidate: 3600 } }); return res.json(); } export default async function Page() { const data = await getData(); return <div>{data.title}</div>; }
4. Server Components vs Client Components
App Router 默认使用 Server Components:
javascript// app/page.js (Server Component - 默认) async function Page() { const data = await fetch('https://api.example.com/data').then(r => r.json()); return <div>{data.title}</div>; } export default Page; // app/components/Interactive.js (Client Component) 'use client'; import { useState } from 'react'; export default function Interactive() { const [count, setCount] = useState(0); return <button onClick={() => setCount(c => c + 1)}>{count}</button>; }
5. 路由参数
Pages Router:
javascript// pages/posts/[id].js export async function getStaticPaths() { const posts = await getAllPosts(); return { paths: posts.map(post => ({ params: { id: post.id } })), fallback: 'blocking' }; } export async function getStaticProps({ params }) { const post = await getPost(params.id); return { props: { post } }; }
App Router:
javascript// app/posts/[id]/page.js export async function generateStaticParams() { const posts = await getAllPosts(); return posts.map(post => ({ id: post.id })); } export default async function PostPage({ params }) { const post = await getPost(params.id); return <article>{post.content}</article>; }
6. 加载状态和错误处理
App Router 内置支持:
javascript// app/posts/loading.js export default function Loading() { return <div>加载中...</div>; } // app/posts/error.js 'use client'; export default function Error({ error, reset }) { return ( <div> <h2>出错了!</h2> <button onClick={() => reset()}>重试</button> </div> ); } // app/posts/not-found.js export default function NotFound() { return <div>文章未找到</div>; }
7. Server Actions
App Router 独有功能:
javascript// app/actions.js 'use server'; import { revalidatePath } from 'next/cache'; export async function createPost(formData) { const title = formData.get('title'); const content = formData.get('content'); await savePost({ title, content }); revalidatePath('/posts'); } // app/posts/new/page.js import { createPost } from '../actions'; export default function NewPostPage() { return ( <form action={createPost}> <input name="title" /> <textarea name="content" /> <button type="submit">创建文章</button> </form> ); }
迁移策略
渐进式迁移
javascript// 可以在同一个项目中同时使用两个 Router // pages/ - 旧代码继续使用 Pages Router // app/ - 新功能使用 App Router // 在 App Router 中链接到 Pages Router <Link href="/old-page">旧页面</Link>
关键迁移步骤
- 安装 Next.js 13+ 并更新配置
- 创建
app/目录结构 - 迁移布局到
app/layout.js - 将页面逐步迁移到
app/目录 - 更新数据获取逻辑
- 添加
'use client'指令到交互式组件 - 更新 API 路由为 Route Handlers
- 测试并优化性能
性能对比
App Router 优势:
- 更小的客户端包(Server Components 不发送到客户端)
- 更好的 SEO(默认服务端渲染)
- 更灵活的缓存策略
- 内置加载和错误状态
- 更简洁的代码结构
Pages Router 优势:
- 更成熟稳定
- 更丰富的生态系统和文档
- 更简单的学习曲线
- 更多的第三方库支持
最佳实践
- 新项目: 优先使用 App Router
- 现有项目: 渐进式迁移,先迁移简单页面
- 组件选择: 默认使用 Server Components,只在需要交互时使用 Client Components
- 数据获取: 利用 fetch API 的缓存选项
- 状态管理: Server Actions 替代部分 API 路由
App Router 代表了 Next.js 的未来方向,提供了更现代的开发体验和更好的性能表现。