Next.js 14 数据处理:从服务端组件到状态管理的最佳实践
Next.js 14 带来了全新的数据处理范式,特别是在服务端组件和数据获取方面有了重大改进。今天,我们就来深入探讨如何在 Next.js 14 中进行高效的数据处理和状态管理。
Server Components 数据获取
1. 基础数据获取
Next.js 14 提供了多种数据获取方式,默认在服务端组件中执行:
// app/posts/page.tsx
async function getPosts() {
// 默认缓存 GET 请求
const posts = await fetch('https://api.example.com/posts');
// 强制重新获取数据
const dynamicData = await fetch('https://api.example.com/stats', {
cache: 'no-store'
});
// 增量静态再生成(ISR)
const revalidatedData = await fetch('https://api.example.com/content', {
next: { revalidate: 3600 } // 1小时后重新验证
});
return {
posts: await posts.json(),
stats: await dynamicData.json(),
content: await revalidatedData.json()
};
}
export default async function PostsPage() {
const { posts, stats, content } = await getPosts();
return (
<div>
<Stats data={stats} />
<PostList posts={posts} />
<Content data={content} />
</div>
);
}
2. 并行数据请求
// app/dashboard/page.tsx
async function DashboardPage() {
// 并行发起多个请求
const [users, orders, analytics] = await Promise.all([
fetch('https://api.example.com/users').then(res => res.json()),
fetch('https://api.example.com/orders').then(res => res.json()),
fetch('https://api.example.com/analytics').then(res => res.json())
]);
return (
<div className="dashboard">
<UserStats data={users} />
<OrderList orders={orders} />
<AnalyticsChart data={analytics} />
</div>
);
}
3. 流式数据加载
// app/feed/page.tsx
import { Suspense } from 'react';
async function SlowComponent() {
const data = await fetch('https://api.example.com/slow-data');
return <SlowDataView data={await data.json()} />;
}
export default function FeedPage() {
return (
<div className="feed">
{/* 快速加载的内容 */}
<Header />
{/* 使用 Suspense 包裹慢速组件 */}
<Suspense fallback={<LoadingSkeleton />}>
<SlowComponent />
</Suspense>
{/* 继续显示其他内容 */}
<Sidebar />
</div>
);
}
数据缓存策略
1. 请求去重和缓存
// lib/data.ts
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
const res = await fetch(`https://api.example.com/users/${id}`);
return res.json();
});
// app/users/[id]/page.tsx
export default async function UserPage({ params }: { params: { id: string } }) {
// 多次调用相同的请求会被自动去重
const user = await getUser(params.id);
return (
<div>
<UserProfile user={user} />
<UserPosts user={user} />
<UserActivity user={user} />
</div>
);
}
2. 缓存标签和重新验证
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
export async function POST(request: Request) {
const { tag } = await request.json();
// 重新验证特定标签的数据
revalidateTag(tag);
return Response.json({ revalidated: true });
}
// app/products/page.tsx
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { tags: ['products'] } // 使用标签标记请求
});
return res.json();
}
状态管理方案
1. 服务端状态
// lib/db.ts
import { createClient } from '@vercel/postgres';
const db = createClient();
export async function getServerState() {
const { rows } = await db.query('SELECT * FROM app_state');
return rows[0];
}
// app/layout.tsx
export default async function RootLayout({
children
}: {
children: React.ReactNode
}) {
const serverState = await getServerState();
return (
<html>
<body>
<ServerStateProvider initialState={serverState}>
{children}
</ServerStateProvider>
</body>
</html>
);
}
2. 客户端状态
// lib/state.ts
import { create } from 'zustand';
interface AppState {
theme: 'light' | 'dark';
setTheme: (theme: 'light' | 'dark') => void;
user: any;
setUser: (user: any) => void;
}
export const useAppStore = create<AppState>((set) => ({
theme: 'light',
setTheme: (theme) => set({ theme }),
user: null,
setUser: (user) => set({ user })
}));
// components/ThemeToggle.tsx
'use client';
export function ThemeToggle() {
const { theme, setTheme } = useAppStore();
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
);
}
3. 服务端操作(Server Actions)
// app/actions.ts
'use server';
import { revalidateTag } from 'next/cache';
import { db } from '@/lib/db';
export async function updatePost(id: string, data: any) {
try {
// 直接在服务端执行数据库操作
await db.post.update({
where: { id },
data
});
// 重新验证相关数据
revalidateTag('posts');
return { success: true };
} catch (error) {
return { error: error.message };
}
}
// app/posts/[id]/edit/page.tsx
export default function EditPost({ params }: { params: { id: string } }) {
async function handleSubmit(formData: FormData) {
'use server';
const title = formData.get('title');
const content = formData.get('content');
const result = await updatePost(params.id, { title, content });
if (result.error) {
return { error: result.error };
}
redirect(`/posts/${params.id}`);
}
return (
<form action={handleSubmit}>
<input type="text" name="title" />
<textarea name="content" />
<button type="submit">Update Post</button>
</form>
);
}
数据库集成
1. Prisma 集成
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
}
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const prismaClientSingleton = () => {
return new PrismaClient();
};
declare global {
var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
}
const prisma = globalThis.prisma ?? prismaClientSingleton();
export default prisma;
if (process.env.NODE_ENV !== 'production') globalThis.prisma = prisma;
2. API Routes 开发
// app/api/posts/route.ts
import { NextResponse } from 'next/server';
import prisma from '@/lib/prisma';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const published = searchParams.get('published') === 'true';
try {
const posts = await prisma.post.findMany({
where: { published },
include: { author: true }
});
return NextResponse.json(posts);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch posts' },
{ status: 500 }
);
}
}
export async function POST(request: Request) {
try {
const json = await request.json();
const post = await prisma.post.create({
data: json,
include: { author: true }
});
return NextResponse.json(post);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to create post' },
{ status: 500 }
);
}
}
性能优化
1. 数据预取
// app/posts/prefetch.ts
import prisma from '@/lib/prisma';
export async function prefetchPosts() {
// 预取热门文章
const posts = await prisma.post.findMany({
where: {
published: true,
featured: true
},
take: 10
});
return posts;
}
// app/posts/page.tsx
import { Suspense } from 'react';
import { prefetchPosts } from './prefetch';
export default async function PostsPage() {
// 预取数据
const prefetchedPosts = prefetchPosts();
return (
<div>
<Suspense fallback={<LoadingSkeleton />}>
<PostList promise={prefetchedPosts} />
</Suspense>
</div>
);
}
2. 数据分页
// lib/pagination.ts
export async function getPaginatedData(page: number, limit: number) {
const offset = (page - 1) * limit;
const [data, total] = await Promise.all([
prisma.post.findMany({
skip: offset,
take: limit,
orderBy: { createdAt: 'desc' }
}),
prisma.post.count()
]);
return {
data,
metadata: {
total,
currentPage: page,
totalPages: Math.ceil(total / limit),
hasNextPage: offset + limit < total
}
};
}
// app/posts/page.tsx
export default async function PostsPage({
searchParams
}: {
searchParams: { page: string }
}) {
const page = parseInt(searchParams.page) || 1;
const { data, metadata } = await getPaginatedData(page, 10);
return (
<div>
<PostList posts={data} />
<Pagination {...metadata} />
</div>
);
}
写在最后
Next.js 14 的数据处理和状态管理方案为我们提供了强大而灵活的工具。在实际应用中,需要注意以下几点:
- 合理使用服务端组件和客户端组件
- 正确配置数据缓存策略
- 选择合适的状态管理方案
- 注意数据预取和性能优化
- 合理组织 API 路由
在下一篇文章中,我们将深入探讨 Next.js 14 的认证与授权实现。如果你有任何问题或建议,欢迎在评论区讨论!
如果觉得这篇文章对你有帮助,别忘了点个赞 👍