Next.js 是 React 生态中最受欢迎的全栈框架,Next.js 14 带来了许多革命性的特性。本文将带你全面了解这些新特性。
App Router:全新的路由系统
文件系统路由
app/
├── layout.tsx # 根布局
├── page.tsx # 首页 (/)
├── about/
│ └── page.tsx # 关于页 (/about)
├── blog/
│ ├── page.tsx # 博客列表 (/blog)
│ └── [slug]/
│ └── page.tsx # 博客详情 (/blog/xxx)
└── api/
└── users/
└── route.ts # API 路由 (/api/users)
布局嵌套
// app/layout.tsx - 根布局
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh-CN">
<body>
<header>导航栏</header>
<main>{children}</main>
<footer>页脚</footer>
</body>
</html>
);
}
// app/dashboard/layout.tsx - 嵌套布局
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex">
<aside>侧边栏</aside>
<div className="flex-1">{children}</div>
</div>
);
}
Server Components:性能革命
默认服务端渲染
// app/posts/page.tsx
// 这是一个 Server Component(默认)
async function PostsPage() {
// 直接在组件中获取数据
const posts = await fetch('https://api.example.com/posts', {
cache: 'force-cache', // 默认缓存
}).then(res => res.json());
return (
<div>
<h1>博客文章</h1>
<ul>
{posts.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
export default PostsPage;
客户端组件
'use client'; // 声明为客户端组件
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
点击次数: {count}
</button>
);
}
Server/Client 组件边界
// app/page.tsx (Server Component)
import Counter from './Counter'; // Client Component
export default async function Page() {
const data = await fetchData(); // 服务端获取数据
return (
<div>
<h1>{data.title}</h1>
<Counter /> {/* 客户端交互组件 */}
</div>
);
}
Server Actions:告别 API 路由
表单处理
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
// 直接操作数据库
await db.post.create({
data: { title, content },
});
// 重新验证页面缓存
revalidatePath('/posts');
}
// app/posts/new/page.tsx
import { createPost } from '../actions';
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" placeholder="标题" required />
<textarea name="content" placeholder="内容" required />
<button type="submit">发布</button>
</form>
);
}
带状态反馈的表单
'use client';
import { useFormStatus, useFormState } from 'react-dom';
import { createPost } from './actions';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? '提交中...' : '提交'}
</button>
);
}
export default function PostForm() {
const [state, formAction] = useFormState(createPost, { error: null });
return (
<form action={formAction}>
<input name="title" required />
<textarea name="content" required />
{state.error && <p className="text-red-500">{state.error}</p>}
<SubmitButton />
</form>
);
}
数据获取策略
静态数据获取
// 构建时获取,可被 CDN 缓存
async function StaticPage() {
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache',
});
// ...
}
动态数据获取
// 每次请求时获取
async function DynamicPage() {
const data = await fetch('https://api.example.com/data', {
cache: 'no-store',
});
// ...
}
增量静态再生成 (ISR)
// 每 60 秒重新验证
async function ISRPage() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 60 },
});
// ...
}
中间件与路由保护
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('token');
// 保护 /dashboard 路由
if (request.nextUrl.pathname.startsWith('/dashboard')) {
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*'],
};
并行路由与插槽
app/
├── layout.tsx
├── page.tsx
├── @modal/
│ └── login/
│ └── page.tsx
└── @sidebar/
└── default.tsx
// app/layout.tsx
export default function Layout({
children,
modal,
sidebar,
}: {
children: React.ReactNode;
modal: React.ReactNode;
sidebar: React.ReactNode;
}) {
return (
<div className="flex">
{sidebar}
<main className="flex-1">
{children}
{modal}
</main>
</div>
);
}
最佳实践
1. 组件服务端化
优先使用 Server Components,只在需要交互时使用 Client Components:
// ✅ 好的做法
// page.tsx (Server Component)
import InteractiveButton from './InteractiveButton';
export default async function Page() {
const data = await getData();
return (
<div>
<StaticContent data={data} />
<InteractiveButton /> {/* 只有这个需要客户端 */}
</div>
);
}
2. 数据获取靠近使用处
// ✅ 在需要数据的组件中获取
async function UserProfile({ userId }: { userId: string }) {
const user = await getUser(userId);
return <div>{user.name}</div>;
}
async function UserPosts({ userId }: { userId: string }) {
const posts = await getPosts(userId);
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}
// 并行获取数据
export default function UserPage({ params }: { params: { id: string } }) {
return (
<>
<UserProfile userId={params.id} />
<UserPosts userId={params.id} />
</>
);
}
3. 错误处理
// app/dashboard/error.tsx
'use client';
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>出错了!</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>重试</button>
</div>
);
}
总结
Next.js 14 带来的新特性让全栈开发变得更加简单:
- App Router - 更直观的文件系统路由
- Server Components - 默认服务端渲染,性能更优
- Server Actions - 简化的后端交互
- 流式渲染 - 更好的用户体验
- 并行路由 - 复杂布局的优雅解决方案
拥抱这些新特性,开启现代全栈开发之旅!