Next.js Server Components数据获取 #
一、服务端组件数据获取 #
1.1 异步组件 #
Server Components 支持 async/await:
tsx
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`)
return res.json()
}
export default async function UserPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const user = await getUser(id)
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}
1.2 直接访问数据库 #
tsx
import { db } from '@/lib/db'
export default async function Page() {
const users = await db.user.findMany()
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
1.3 使用ORM #
tsx
import { prisma } from '@/lib/prisma'
export default async function Page() {
const posts = await prisma.post.findMany({
include: { author: true },
orderBy: { createdAt: 'desc' },
take: 10,
})
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>作者: {post.author.name}</p>
</article>
))}
</div>
)
}
二、并行数据获取 #
2.1 Promise.all #
tsx
export default async function Dashboard() {
const [users, posts, stats] = await Promise.all([
fetch('https://api.example.com/users').then(res => res.json()),
fetch('https://api.example.com/posts').then(res => res.json()),
fetch('https://api.example.com/stats').then(res => res.json()),
])
return (
<div>
<UsersWidget users={users} />
<PostsWidget posts={posts} />
<StatsWidget stats={stats} />
</div>
)
}
2.2 预加载数据 #
tsx
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`)
return res.json()
}
async function getUserPosts(userId: string) {
const res = await fetch(`https://api.example.com/users/${userId}/posts`)
return res.json()
}
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const userPromise = getUser(id)
const postsPromise = userPromise.then(user => getUserPosts(user.id))
const [user, posts] = await Promise.all([userPromise, postsPromise])
return (
<div>
<h1>{user.name}</h1>
<PostsList posts={posts} />
</div>
)
}
2.3 数据依赖 #
tsx
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`)
return res.json()
}
async function getPermissions(userId: string) {
const res = await fetch(`https://api.example.com/users/${userId}/permissions`)
return res.json()
}
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const user = await getUser(id)
const permissions = await getPermissions(user.id)
return (
<div>
<h1>{user.name}</h1>
<PermissionsList permissions={permissions} />
</div>
)
}
三、流式渲染 #
3.1 Suspense基础 #
tsx
import { Suspense } from 'react'
async function SlowComponent() {
const data = await fetch('https://api.example.com/slow').then(res => res.json())
return <div>{data.content}</div>
}
export default function Page() {
return (
<div>
<h1>页面标题</h1>
<Suspense fallback={<div>加载中...</div>}>
<SlowComponent />
</Suspense>
</div>
)
}
3.2 多个Suspense #
tsx
import { Suspense } from 'react'
async function Users() {
const users = await fetch('https://api.example.com/users').then(res => res.json())
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}
async function Posts() {
const posts = await fetch('https://api.example.com/posts').then(res => res.json())
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}
async function Stats() {
const stats = await fetch('https://api.example.com/stats').then(res => res.json())
return <div>{stats.views} 次浏览</div>
}
export default function Page() {
return (
<div className="grid gap-4">
<Suspense fallback={<UsersSkeleton />}>
<Users />
</Suspense>
<Suspense fallback={<PostsSkeleton />}>
<Posts />
</Suspense>
<Suspense fallback={<StatsSkeleton />}>
<Stats />
</Suspense>
</div>
)
}
3.3 骨架屏 #
tsx
function UsersSkeleton() {
return (
<div className="animate-pulse space-y-2">
{[...Array(5)].map((_, i) => (
<div key={i} className="h-4 bg-gray-200 rounded w-3/4"></div>
))}
</div>
)
}
function PostsSkeleton() {
return (
<div className="animate-pulse space-y-4">
{[...Array(3)].map((_, i) => (
<div key={i}>
<div className="h-4 bg-gray-200 rounded w-1/2 mb-2"></div>
<div className="h-3 bg-gray-200 rounded w-full"></div>
</div>
))}
</div>
)
}
3.4 loading.tsx #
tsx
export default function Loading() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div>
</div>
)
}
四、数据获取模式 #
4.1 单一数据源 #
tsx
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 60 },
})
if (!res.ok) {
throw new Error('Failed to fetch post')
}
return res.json()
}
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const post = await getPost(slug)
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
)
}
4.2 组合数据 #
tsx
interface PageData {
user: User
posts: Post[]
stats: Stats
}
async function getPageData(userId: string): Promise<PageData> {
const [userRes, postsRes, statsRes] = await Promise.all([
fetch(`https://api.example.com/users/${userId}`),
fetch(`https://api.example.com/users/${userId}/posts`),
fetch(`https://api.example.com/users/${userId}/stats`),
])
const [user, posts, stats] = await Promise.all([
userRes.json(),
postsRes.json(),
statsRes.json(),
])
return { user, posts, stats }
}
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const { user, posts, stats } = await getPageData(id)
return (
<div>
<UserHeader user={user} />
<UserStats stats={stats} />
<UserPosts posts={posts} />
</div>
)
}
4.3 分页数据 #
tsx
interface PageProps {
params: Promise<{}>
searchParams: Promise<{ page?: string }>
}
export default async function PostsPage({ searchParams }: PageProps) {
const { page = '1' } = await searchParams
const pageNum = parseInt(page, 10)
const res = await fetch(`https://api.example.com/posts?page=${pageNum}`)
const { posts, totalPages } = await res.json()
return (
<div>
<PostsList posts={posts} />
<Pagination currentPage={pageNum} totalPages={totalPages} />
</div>
)
}
五、缓存策略 #
5.1 页面级缓存 #
tsx
export const revalidate = 60
export default async function Page() {
const data = await fetch('https://api.example.com/data').then(res => res.json())
return <div>{data.content}</div>
}
5.2 请求级缓存 #
tsx
export default async function Page() {
const staticData = await fetch('https://api.example.com/static', {
cache: 'force-cache',
}).then(res => res.json())
const dynamicData = await fetch('https://api.example.com/dynamic', {
cache: 'no-store',
}).then(res => res.json())
const revalidatedData = await fetch('https://api.example.com/periodic', {
next: { revalidate: 60 },
}).then(res => res.json())
return (
<div>
<StaticSection data={staticData} />
<DynamicSection data={dynamicData} />
<RevalidatedSection data={revalidatedData} />
</div>
)
}
5.3 段配置 #
tsx
export const dynamic = 'force-dynamic'
export const revalidate = 0
export default async function Page() {
const data = await fetch('https://api.example.com/data').then(res => res.json())
return <div>{data.content}</div>
}
六、错误处理 #
6.1 组件级错误 #
tsx
async function SafeComponent() {
try {
const data = await fetch('https://api.example.com/data').then(res => res.json())
return <div>{data.content}</div>
} catch (error) {
return <div>加载失败</div>
}
}
export default function Page() {
return (
<Suspense fallback={<div>加载中...</div>}>
<SafeComponent />
</Suspense>
)
}
6.2 错误边界 #
tsx
import { ErrorBoundary } from 'react-error-boundary'
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div>
<p>出错了: {error.message}</p>
<button onClick={resetErrorBoundary}>重试</button>
</div>
)
}
export default function Page() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<DataComponent />
</ErrorBoundary>
)
}
七、最佳实践 #
7.1 数据获取函数封装 #
tsx
export async function getPost(slug: string) {
const res = await fetch(`${API_URL}/posts/${slug}`, {
next: { revalidate: 3600 },
})
if (!res.ok) {
if (res.status === 404) {
notFound()
}
throw new Error('Failed to fetch post')
}
return res.json()
}
export async function getPosts() {
const res = await fetch(`${API_URL}/posts`, {
next: { revalidate: 3600 },
})
if (!res.ok) {
throw new Error('Failed to fetch posts')
}
return res.json()
}
7.2 类型安全 #
tsx
interface Post {
id: string
title: string
content: string
author: {
id: string
name: string
}
createdAt: string
}
export async function getPost(slug: string): Promise<Post> {
const res = await fetch(`${API_URL}/posts/${slug}`)
if (!res.ok) {
throw new Error('Failed to fetch post')
}
return res.json()
}
7.3 环境变量 #
tsx
const API_URL = process.env.API_URL!
export async function getData() {
const res = await fetch(`${API_URL}/data`)
return res.json()
}
八、总结 #
Server Components数据获取要点:
| 要点 | 说明 |
|---|---|
| 异步组件 | async/await |
| 并行获取 | Promise.all |
| 流式渲染 | Suspense |
| 缓存策略 | revalidate/cache |
| 错误处理 | try/catch + ErrorBoundary |
下一步,让我们学习Client Components数据获取!
最后更新:2026-03-28