← 返回文章列表
Next.js App Router 全面指南:从 Pages 迁移到 App
全面介绍 Next.js 13+ App Router 的核心概念,包括 Server Components、Streaming、缓存策略等,以及从 Pages Router 迁移的注意事项。
2025年3月15日·4 分钟阅读
字号
Next.js App Router 是 React 生态的一次重要演进。它基于 React Server Components(RSC)构建,带来了更强大的数据获取模式和更细粒度的缓存控制。
核心概念:Server Components vs Client Components
默认情况下,App Router 中的所有组件都是 Server Components:
// app/blog/page.tsx — Server Component(默认)
// 可以直接 await,无需 useEffect + useState
export default async function BlogPage() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}需要使用浏览器 API、state 或事件处理时,添加 "use client" 指令:
"use client";
// 现在这是 Client Component
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}文件约定(File Conventions)
App Router 用特定文件名约定来定义路由行为:
app/
layout.tsx # 共享布局,不会在导航时重新渲染
page.tsx # 路由页面,公开可访问
loading.tsx # 加载 UI(React Suspense 边界)
error.tsx # 错误 UI(React Error Boundary)
not-found.tsx # 404 页面
template.tsx # 类似 layout,但每次导航都重新挂载数据获取最佳实践
并行获取
// ❌ 串行:总时间 = 请求1 + 请求2
async function Page() {
const user = await getUser();
const posts = await getPosts(user.id);
// ...
}
// ✅ 并行:总时间 = max(请求1, 请求2)
async function Page() {
const [user, posts] = await Promise.all([getUser(), getAllPosts()]);
// ...
}流式渲染(Streaming)
使用 Suspense 逐步呈现内容,不阻塞整个页面:
import { Suspense } from "react";
export default function Page() {
return (
<div>
<h1>博客</h1>
{/* 立即显示,不等待 */}
<Suspense fallback={<PostSkeleton />}>
<PostList /> {/* 这个可以慢慢加载 */}
</Suspense>
</div>
);
}缓存策略
这是 App Router 最复杂也最强大的部分:
// 默认缓存(构建时生成静态内容)
const data = await fetch('https://api.example.com/posts');
// 不缓存(每次请求都重新获取)
const data = await fetch('https://api.example.com/posts', {
cache: 'no-store'
});
// 定时重新验证(每小时刷新一次)
const data = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
});
// 按需重新验证
import { revalidatePath, revalidateTag } from 'next/cache';
export async function action() {
// 触发后清除缓存
revalidatePath('/blog');
revalidateTag('posts');
}Route Handlers(API Routes 替代)
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const tag = searchParams.get('tag');
const posts = await getPostsByTag(tag);
return NextResponse.json(posts);
}
export async function POST(request: NextRequest) {
const body = await request.json();
const post = await createPost(body);
return NextResponse.json(post, { status: 201 });
}Metadata API
静态和动态 SEO 配置变得非常简洁:
// 静态 metadata
export const metadata = {
title: '我的博客',
description: '技术文章分享',
openGraph: {
title: '我的博客',
images: ['/og-image.png'],
},
};
// 动态 metadata
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.description,
};
}从 Pages Router 迁移注意事项
| Pages Router | App Router |
|---|---|
getServerSideProps | 直接在 async 组件中 await |
getStaticProps | fetch + cache: 'force-cache' |
getStaticPaths | generateStaticParams |
useRouter().pathname | usePathname() |
_app.tsx | app/layout.tsx |
| API Routes | Route Handlers |
App Router 是 Next.js 的未来方向,虽然迁移有一定成本,但它带来的数据获取模式和性能提升是值得的。
分享