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

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 (

{children}

);
}

// 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}

{post.content}

);
}


第三章:服务端组件 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 (

{loading ? (
加载中…
) : (

实时数据

最新值:{data.value}

)}

);
}

// 客户端组件的使用场景:
// ✅ 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 (

{/* 服务端渲染的静态内容 */}

{/* 服务端组件:统计数据 */}

{/* 客户端组件:交互功能 */}


location.reload()} />

);
}

// app/dashboard/stats-section.js – 服务端组件
import StatCard from ‘@/components/stat-card’;

export async function StatsSection({ data }) {
return (

统计数据

{Object.entries(data).map(([key, value]) => (

))}

);
}

// 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 (

产品 1
产品 2

);
}

// 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 应用的下一代架构:

核心优势:

  1. 更优的性能:服务端组件零 bundle 开销
  2. 更好的 DX:文件系统路由,直观易学
  3. 更强的功能:流式渲染,部分渲染
  4. 更清晰的架构:Server/Client 组件明确分离
  5. 最佳实践:

    • ✅ Server Component 获取数据
    • ✅ Client Component 处理交互
    • ✅ 并行数据获取
    • ✅ 合理配置缓存
    • ✅ 使用 Suspense 边界

    未来趋势:

    • Server Components 成为默认
    • 更强大的流式渲染
    • 更好的 TypeScript 支持

    掌握 App Router,让你的 React 应用性能提升一个数量级!🚀

    参考资源:

    • [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)

标签

发表评论