Next.js 14 App Router 实战:服务端组件与客户端组件的最佳搭配

Next.js 14 App Router 实战:服务端组件与客户端组件的最佳搭配
引言:Next.js 的重新定义
如果你还在使用 Pages Router,那么 Next.js 14 的 App Router 将彻底改变你的开发体验。
作为 React Server Components 的官方实现,App Router 代表了 Next.js 架构的重大革新。今天这篇教程将带你全面掌握 App Router,让你写出更强大、更高效的应用。
第一章:App Router 核心概念
1.1 Pages Router vs App Router
Pages Router(传统):
- 基于路由的文件系统
- 组件默认是客户端组件
- 数据获取使用 getServerSideProps/getStaticProps
- 路由:pages/posts/[id].js
App Router(新一代):
- 基于目录结构的路由
- 支持服务端组件和客户端组件
- 数据获取直接 async/await
- 路由:app/posts/[id]/page.js
- 布局:app/posts/[id]/layout.js
1.2 核心架构
┌─────────────────────────────────────────────────┐
│ App Router │
├─────────────────────────────────────────────────┤
│ Layout (布局组件 - 服务器端) │
│ ├─ Page (页面组件 - 服务器端) │
│ │ └─ Client Component (客户端组件) │
│ └─ Loading (加载状态) │
│ └─ Error (错误边界) │
│ └─ Global Error (全局错误边界) │
└─────────────────────────────────────────────────┘
第二章:路由系统实战
2.1 基本路由结构
app/
├── layout.js # 根布局
├── page.js # 首页
├── about/
│ └── page.js # /about
├── posts/
│ ├── page.js # /posts
│ ├── [slug]/
│ │ └── page.js # /posts/[slug]
│ └── [slug]/
│ └── edit/
│ └── page.js # /posts/[slug]/edit
└── api/
└── users/
└── route.js # /api/users
2.2 页面路由实现
“`javascript
// app/page.js – 首页
export default function HomePage() {
return (
欢迎使用 Next.js 14
这是一个静态页面
);
}
// app/about/page.js – 关于页面
export default function AboutPage() {
return (
关于我们
我们是一个充满激情的团队…
);
}
// app/posts/[slug]/page.js – 动态路由
export default async function PostPage({ params }) {
const { slug } = await params; // Next.js 14 支持 await params
const post = await fetchPost(slug);
return (
{post.title}
);
}
// 可选动态段
export function generateStaticParams() {
return [{ slug: ‘hello-world’ }];
}
2.3 嵌套路由与布局
javascript
// app/posts/layout.js – 嵌套布局
export default function PostsLayout({ children }) {
return (
);
}
// app/posts/page.js – 列表页面
export default function PostsListPage() {
return (
文章列表
);
}
// app/posts/[slug]/page.js – 单篇文章
export default async function PostPage({ params }) {
const { slug } = await params;
const post = await fetchPost(slug);
return (
{post.title}
);
}
第三章:服务端组件 vs 客户端组件
3.1 服务端组件(Server Components)
javascript
// app/dashboard/stats/page.js – 服务端组件
// 默认是服务端组件,无需特殊标记
import db from ‘@/lib/db’;
export default async function StatsPage() {
// 直接访问数据库,无需 API
const stats = await db.stats.get({
users: true,
orders: true,
revenue: true
});
return (
);
}
// 服务端组件的优势:
// ✅ 代码不传输到客户端
// ✅ 直接访问数据库和服务器资源
// ✅ 零 bundle size 影响
// ✅ 支持 async/await
3.2 客户端组件(Client Components)
javascript
‘use client’; // 必须标记
import { useState, useEffect } from ‘react’;
export function InteractiveWidget({ initialData }) {
const [data, setData] = useState(initialData);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 轮询更新数据
const interval = setInterval(async () => {
const response = await fetch(‘/api/widget-data’);
const newData = await response.json();
setData(newData);
setLoading(false);
}, 5000);
return () => clearInterval(interval);
}, []);
return (
加载中…
) : (
实时数据
)}
);
}
// 客户端组件的使用场景:
// ✅ useState, useEffect
// ✅ 事件处理 (onClick, onChange)
// ✅ 浏览器 API (localStorage, window)
// ✅ useRef, useReducer
3.3 混合使用
javascript
// app/dashboard/page.js – 混合布局
// 服务端组件:获取数据
export default async function DashboardPage() {
const dashboardData = await fetchDashboardData();
return (
{/* 服务端组件:统计数据 */}
{/* 客户端组件:交互功能 */}
);
}
// app/dashboard/stats-section.js – 服务端组件
import StatCard from ‘@/components/stat-card’;
export async function StatsSection({ data }) {
return (
统计数据
))}
);
}
// app/dashboard/live-chart.js – 客户端组件
‘use client’;
import { useRef, useEffect } from ‘react’;
import { Chart } from ‘chart.js’;
export function LiveChart({ data }) {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext(‘2d’);
const chart = new Chart(ctx, {
type: ‘line’,
data: { labels: data.labels, datasets: data.datasets },
options: {
animation: {
duration: 0
}
}
});
return () => chart.destroy();
}, [data]);
return ;
}
第四章:数据获取模式
4.1 服务端数据获取
javascript
// app/products/[id]/page.js
export default async function ProductPage({ params }) {
// 直接 fetch,自动缓存和重新验证
const response = await fetch(
`https://api.example.com/products/${await params.id}`,
{
next: { revalidate: 3600 } // 每小时重新验证
}
);
const product = await response.json();
return (
{product.name}
{product.description}
价格:¥{product.price}
);
}
// 可选:指定缓存标签
export async function generateMetadata({ params }) {
const product = await fetchProduct(await params.id);
return {
title: product.name,
description: product.description
};
}
4.2 并行数据获取
javascript
// app/profile/[id]/page.js
export default async function ProfilePage({ params }) {
const userId = await params.id;
// 并行获取多个数据源
const [user, posts, stats, notifications] = await Promise.all([
fetchUser(userId),
fetchUserPosts(userId),
fetchUserStats(userId),
fetchUserNotifications(userId)
]);
return (
);
}
// 性能对比
// 串行:200ms + 150ms + 100ms + 80ms = 530ms
// 并行:Math.max(200, 150, 100, 80) = 200ms
// 性能提升:62%
4.3 缓存与重新验证
javascript
// 强制重新验证
export default async function Page({ searchParams }) {
const { force } = await searchParams;
const response = await fetch(‘https://api.example.com/data’, {
next: { revalidate: 0, tags: [‘data’] } // 无缓存,标签管理
});
if (force === ‘true’) {
revalidateTag(‘data’); // 重新验证标签
}
const data = await response.json();
return
}
// 多标签管理
const response = await fetch(url, {
next: { tags: [‘users’, ‘posts’, ‘comments’] }
});
4.4 流式渲染
javascript
// app/dashboard/page.js
import { Suspense } from ‘react’;
import { Loading } from ‘@/components/loading’;
export default async function DashboardPage() {
return (
{/* 立即显示其他内容,等待慢组件 */}
);
}
// app/dashboard/slow-component.js
export default async function SlowComponent() {
// 模拟慢查询
await new Promise(resolve => setTimeout(resolve, 3000));
return
;
}
第五章:性能优化实战
5.1 Bundle Size 优化
javascript
// ✅ 使用动态导入
‘use client’;
import dynamic from ‘next/dynamic’;
export default function Page() {
// 按需加载客户端组件
const ChatWidget = dynamic(
() => import(‘@/components/ChatWidget’),
{
loading: ()
ssr: false // 禁用服务端渲染
}
);
const HeavyChart = dynamic(
() => import(‘@/components/HeavyChart’),
{
loading: ()
ssr: true // 允许服务端渲染
}
);
return (
);
}
5.2 图片优化
javascript
// 使用 next/image 优化
import Image from ‘next/image’;
export default function ProductGallery() {
return (
);
}
// next.config.js 配置
module.exports = {
images: {
formats: [‘image/avif’, ‘image/webp’],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
domains: [‘example.com’],
remotePatterns: [
{
protocol: ‘https’,
hostname: ‘images.example.com’
}
]
}
};
5.3 字体优化
javascript
// app/layout.js
import { Inter, Roboto } from ‘next/font/google’;
const inter = Inter({
subsets: [‘latin’],
display: ‘swap’, // 防止 FOIT
variable: ‘–font-inter’
});
export default function RootLayout({ children }) {
return (
{children}
);
}
5.4 服务端缓存策略
javascript
// 使用 cache() 函数
import { cache } from ‘react’;
const getExpensiveData = cache(async (id) => {
// 在同个请求中重复调用返回缓存结果
return await expensiveQuery(id);
});
// 使用 revalidatePath 和 revalidateTag
import { revalidatePath, revalidateTag } from ‘next/cache’;
export async function POST(request) {
const { id } = await request.json();
await saveData(id);
// 重新验证特定路径
revalidatePath(`/posts/${id}`);
// 或重新验证标签
revalidateTag(‘posts’);
}
第六章:API Routes
6.1 路由处理器
javascript
// app/api/users/route.js
import { NextResponse } from ‘next/server’;
// GET 请求
export async function GET(request) {
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get(‘page’) || ‘1’);
const limit = 10;
const users = await getUsers(page, limit);
return NextResponse.json({
data: users,
pagination: { page, limit, total: 100 }
});
}
// POST 请求
export async function POST(request) {
try {
const body = await request.json();
const user = await createUser(body);
return NextResponse.json(user, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: error.message },
{ status: 400 }
);
}
}
// PUT 请求
export async function PUT(request, { params }) {
const { id } = await params;
const body = await request.json();
const user = await updateUser(id, body);
return NextResponse.json(user);
}
// DELETE 请求
export async function DELETE(request, { params }) {
const { id } = await params;
await deleteUser(id);
return NextResponse.json({ success: true });
}
6.2 路由处理器组合
javascript
// app/api/posts/[id]/route.js
import { NextResponse } from ‘next/server’;
const posts = [
{ id: ‘1’, title: ‘Post 1’ },
{ id: ‘2’, title: ‘Post 2’ }
];
export async function GET(request, { params }) {
const { id } = await params;
const post = posts.find(p => p.id === id);
if (!post) {
return NextResponse.json(
{ error: ‘Post not found’ },
{ status: 404 }
);
}
return NextResponse.json(post);
}
// app/api/posts/route.js
export async function GET() {
return NextResponse.json(posts);
}
export async function POST(request) {
const body = await request.json();
const newPost = { id: String(posts.length + 1), …body };
posts.push(newPost);
return NextResponse.json(newPost, { status: 201 });
}
第七章:最佳实践
7.1 组件分工
javascript
// ✅ 好的模式
// Server Component – 数据获取和渲染
export default async function Page() {
const data = await fetchData();
return (
);
}
// ✅ 客户端组件 – 交互
‘use client’;
export function ClientWidget({ data }) {
const [state, setState] = useState();
return
;
}
// ❌ 不好的模式
‘use client’; // 整个页面都在客户端
export default function Page() {
// 浪费客户端资源
return
;
}
7.2 错误处理
javascript
// app/error.js – 全局错误边界
‘use client’;
export default function Error({ error, reset }) {
return (
出错了!
{error.message}
);
}
// app/posts/[slug]/error.js – 页面错误边界
‘use client’;
export default function Error({ error, reset }) {
return (
文章加载失败
);
}
// 组件内错误处理
export default async function Page() {
try {
const data = await fetchData();
return
} catch (error) {
return
}
}
7.3 Loading 状态
javascript
// app/posts/[slug]/loading.js
export default function Loading() {
return (
);
}
// app/dashboard/loading.js – 嵌套 loading
export default function Loading() {
return (
);
}
7.4 优化清单
javascript
// ✅ 优化检查清单
// 1. 优先使用服务端组件
// ✅ Server Component
// ❌ ‘use client’ (除非必要)
// 2. 并行数据获取
// ✅ Promise.all([fetchA(), fetchB()])
// ❌ await fetchA(); await fetchB();
// 3. 合理使用缓存
// ✅ next: { revalidate: 3600 }
// ✅ tags: [‘users’]
// 4. 使用动态导入
// ✅ dynamic(() => import(‘./HeavyComponent’))
// 5. 图片优化
// ✅ next/image
// ✅ priority 首屏
// ✅ loading=”lazy” 非首屏
// 6. 字体优化
// ✅ next/font
// ✅ display: ‘swap’
“`
总结:掌握 App Router,拥抱未来
Next.js 14 App Router 代表了 React 应用的下一代架构:
核心优势:
- 更优的性能:服务端组件零 bundle 开销
- 更好的 DX:文件系统路由,直观易学
- 更强的功能:流式渲染,部分渲染
- 更清晰的架构:Server/Client 组件明确分离
- ✅ Server Component 获取数据
- ✅ Client Component 处理交互
- ✅ 并行数据获取
- ✅ 合理配置缓存
- ✅ 使用 Suspense 边界
- Server Components 成为默认
- 更强大的流式渲染
- 更好的 TypeScript 支持
- [Next.js 官方文档](https://nextjs.org/docs)
- [App Router 文档](https://nextjs.org/docs/app)
- [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components)
- [Next.js 14 发布](https://nextjs.org/blog/next-14)
最佳实践:
未来趋势:
掌握 App Router,让你的 React 应用性能提升一个数量级!🚀
—
参考资源:



发表评论