name: Next.js应用路由器技能 description: 使用App Router架构构建现代Next.js 13+应用,包括服务器组件、客户端组件和高级路由模式。用于实现服务器优先渲染、创建嵌套布局、构建并行路由、实现拦截路由、使用React服务器组件、通过服务器端异步组件优化数据获取、使用Suspense实现流式传输、管理客户端交互边界,或利用Next.js 13+应用目录功能来构建高性能、SEO友好的React应用。
Next.js App Router - 现代React框架模式
何时使用此技能
- 构建使用App Router的Next.js 13+应用
- 实现服务器组件以进行服务器优先渲染
- 创建客户端组件用于交互式UI元素
- 设置跨路由持久化的嵌套布局
- 实现复杂UI模式的并行路由
- 使用拦截路由处理模态框和覆盖层
- 通过异步服务器组件优化数据获取
- 使用Suspense实现流式传输和渐进渲染
- 有效管理客户端/服务器组件边界
- 构建具有服务器端渲染的SEO友好应用
- 利用服务器动作处理表单
- 使用应用目录结构创建基于文件的路由
何时使用此技能
- 构建Next.js 13+应用使用App Router、实现服务器组件或优化React服务器组件模式。
- 在处理相关任务或功能时
- 在需要此专业知识的开发期间
使用时机:构建Next.js 13+应用使用App Router、实现服务器组件或优化React服务器组件模式。
核心概念
- 默认服务器组件 - 组件默认在服务器渲染,除非标记为’use client’
- 基于文件的路由 -
/app目录结构定义路由 - 流式传输与Suspense - 渐进渲染以改善用户体验
- 数据获取 - 服务器端自动缓存
- 布局与模板 - 跨路由共享的UI
App Router 结构
app/
├── layout.tsx # 根布局(必需)
├── page.tsx # 主页(/)
├── loading.tsx # 加载UI
├── error.tsx # 错误UI
├── not-found.tsx # 404 UI
├── dashboard/
│ ├── layout.tsx # 仪表板布局
│ ├── page.tsx # /dashboard
│ └── settings/
│ └── page.tsx # /dashboard/settings
└── api/
└── users/
└── route.ts # API端点 /api/users
服务器与客户端组件
// ✅ 服务器组件(默认 - 无'use client')
// app/page.tsx
import { db } from '@/lib/db';
export default async function Page() {
// 在服务器获取数据
const users = await db.user.findMany();
return (
<div>
<h1>用户</h1>
{users.map(user => <UserCard key={user.id} user={user} />)}
</div>
);
}
// ✅ 客户端组件(交互功能)
// app/components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
计数: {count}
</button>
);
}
// ✅ 组合:服务器组件与客户端组件子项
// app/page.tsx
import { Counter } from './components/Counter';
export default async function Page() {
const data = await fetchData(); // 服务器端
return (
<div>
<h1>服务器数据: {data}</h1>
<Counter /> {/* 客户端交互组件 */}
</div>
);
}
数据获取
// ✅ 获取并缓存(默认:'force-cache')
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'force-cache' // 缓存直到重新验证
});
return res.json();
}
// ✅ 每60秒重新验证
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
next: { revalidate: 60 }
});
return res.json();
}
// ✅ 无缓存(始终新鲜)
async function getUsers() {
const res = await fetch('https://api.example.com/users', {
cache: 'no-store'
});
return res.json();
}
// ✅ 数据库查询(Prisma)
import { db } from '@/lib/db';
async function getUser(id: string) {
return await db.user.findUnique({ where: { id } });
}
动态路由
// app/posts/[id]/page.tsx
interface PageProps {
params: { id: string };
searchParams: { [key: string]: string | string[] | undefined };
}
export default async function PostPage({ params }: PageProps) {
const post = await db.post.findUnique({ where: { id: params.id } });
return <div>{post.title}</div>;
}
// 在构建时生成静态路径
export async function generateStaticParams() {
const posts = await db.post.findMany();
return posts.map((post) => ({ id: post.id }));
}
// 生成元数据
export async function generateMetadata({ params }: PageProps) {
const post = await db.post.findUnique({ where: { id: params.id } });
return {
title: post.title,
description: post.excerpt
};
}
布局
// app/layout.tsx(根布局 - 必需)
export default function RootLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Header />
<main>{children}</main>
<Footer />
</body>
</html>
);
}
// app/dashboard/layout.tsx(嵌套布局)
export default function DashboardLayout({
children
}: {
children: React.ReactNode;
}) {
return (
<div className="dashboard">
<Sidebar />
<div className="content">{children}</div>
</div>
);
}
加载与错误状态
// app/dashboard/loading.tsx
export default function Loading() {
return <Spinner />;
}
// app/dashboard/error.tsx
'use client';
export default function Error({
error,
reset
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>出错了!</h2>
<button onClick={reset}>重试</button>
</div>
);
}
使用Suspense的流式传输
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>仪表板</h1>
{/* 这个立即加载 */}
<QuickStats />
{/* 这个在准备好时流式传输 */}
<Suspense fallback={<Skeleton />}>
<SlowComponent />
</Suspense>
</div>
);
}
async function SlowComponent() {
const data = await slowDatabaseQuery();
return <div>{data}</div>;
}
路由处理程序(API路由)
// app/api/users/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const page = searchParams.get('page') || '1';
const users = await db.user.findMany({
skip: (parseInt(page) - 1) * 20,
take: 20
});
return NextResponse.json({ users });
}
export async function POST(request: Request) {
const body = await request.json();
const user = await db.user.create({
data: body
});
return NextResponse.json(user, { status: 201 });
}
// 动态路由: app/api/users/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const user = await db.user.findUnique({ where: { id: params.id } });
if (!user) {
return NextResponse.json({ error: '未找到' }, { status: 404 });
}
return NextResponse.json(user);
}
最佳实践
- 最小化客户端组件 - 仅在需要时使用’use client’(交互性、钩子)
- 在服务器获取数据 - 减少客户端捆绑包,提高安全性
- 流式传输 - 使用Suspense进行渐进渲染
- 元数据 - 为每个路由生成SEO友好的元数据
- 并行路由 - 使用
@folder约定进行并行渲染 - 路由组 - 使用
(folder)进行组织而不影响URL
资源
记住:App Router是服务器优先的。拥抱服务器组件,谨慎使用客户端组件,并利用流式传输改善用户体验。