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