Next.js数据获取基础 #

一、数据获取概述 #

1.1 获取方式 #

Next.js 提供了多种数据获取方式:

方式 适用场景 执行位置
fetch API 服务端组件 服务端
ORM/数据库 服务端组件 服务端
第三方库 服务端/客户端 取决于库
SWR/React Query 客户端组件 客户端

1.2 扩展fetch #

Next.js 扩展了原生 fetch API,增加了缓存和重新验证功能:

tsx
async function getData() {
    const res = await fetch('https://api.example.com/data', {
        cache: 'force-cache',
    })
    return res.json()
}

二、fetch缓存选项 #

2.1 自动缓存 #

默认情况下,fetch 请求会被缓存:

tsx
async function getData() {
    const res = await fetch('https://api.example.com/data')
    return res.json()
}

等效于:

tsx
async function getData() {
    const res = await fetch('https://api.example.com/data', {
        cache: 'force-cache',
    })
    return res.json()
}

2.2 禁用缓存 #

tsx
async function getData() {
    const res = await fetch('https://api.example.com/data', {
        cache: 'no-store',
    })
    return res.json()
}

2.3 定时重新验证 #

tsx
async function getData() {
    const res = await fetch('https://api.example.com/data', {
        next: { revalidate: 60 },
    })
    return res.json()
}

2.4 缓存选项对比 #

选项 说明 适用场景
cache: 'force-cache' 永久缓存 静态数据
cache: 'no-store' 不缓存 实时数据
next: { revalidate: 60 } 60秒后重新验证 半动态数据

三、请求去重 #

3.1 自动去重 #

Next.js 自动对相同的请求进行去重:

tsx
async function getUser() {
    const res = await fetch('https://api.example.com/user')
    return res.json()
}

export default async function Page() {
    const user1 = await getUser()
    const user2 = await getUser()
    
    return <div>{user1.name}</div>
}

上述代码只会发送一次请求。

3.2 去重原理 #

Next.js 使用请求 URL 和选项作为缓存键:

tsx
fetch('https://api.example.com/data', {
    headers: { 'Authorization': 'Bearer token' },
})

不同的 headers 会产生不同的缓存。

3.3 AbortController #

tsx
async function getData() {
    const controller = new AbortController()
    const timeoutId = setTimeout(() => controller.abort(), 5000)
    
    try {
        const res = await fetch('https://api.example.com/data', {
            signal: controller.signal,
        })
        return res.json()
    } finally {
        clearTimeout(timeoutId)
    }
}

四、静态数据获取 #

4.1 构建时获取 #

tsx
async function getStaticData() {
    const res = await fetch('https://api.example.com/static-data', {
        cache: 'force-cache',
    })
    return res.json()
}

export default async function Page() {
    const data = await getStaticData()
    
    return <div>{data.content}</div>
}

4.2 generateStaticParams #

tsx
export async function generateStaticParams() {
    const posts = await fetch('https://api.example.com/posts').then(res => res.json())
    
    return posts.map(post => ({
        slug: post.slug,
    }))
}

export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
    const { slug } = await params
    const post = await fetch(`https://api.example.com/posts/${slug}`).then(res => res.json())
    
    return <article>{post.content}</article>
}

五、动态数据获取 #

5.1 每次请求获取 #

tsx
async function getDynamicData() {
    const res = await fetch('https://api.example.com/dynamic-data', {
        cache: 'no-store',
    })
    return res.json()
}

export default async function Page() {
    const data = await getDynamicData()
    
    return <div>{data.content}</div>
}

5.2 动态函数 #

使用动态函数会自动禁用缓存:

tsx
import { cookies, headers } from 'next/headers'

export default async function Page() {
    const cookieStore = await cookies()
    const headersList = await headers()
    
    const token = cookieStore.get('token')
    const userAgent = headersList.get('user-agent')
    
    const data = await fetch('https://api.example.com/data', {
        headers: { Authorization: `Bearer ${token}` },
    }).then(res => res.json())
    
    return <div>{data.content}</div>
}

5.3 动态配置 #

tsx
export const dynamic = 'force-dynamic'

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 getData() {
    const res = await fetch('https://api.example.com/data', {
        next: { revalidate: 60 },
    })
    return res.json()
}

export default async function Page() {
    const data = await getData()
    
    return <div>{data.content}</div>
}

6.2 按需重新验证 #

基于标签

tsx
async function getData() {
    const res = await fetch('https://api.example.com/data', {
        next: { tags: ['collection'] },
    })
    return res.json()
}

触发重新验证:

tsx
import { revalidateTag } from 'next/cache'

export async function POST() {
    revalidateTag('collection')
    return Response.json({ revalidated: true })
}

基于路径

tsx
import { revalidatePath } from 'next/cache'

export async function POST() {
    revalidatePath('/blog')
    return Response.json({ revalidated: true })
}

七、错误处理 #

7.1 基本错误处理 #

tsx
export default async function Page() {
    try {
        const res = await fetch('https://api.example.com/data')
        
        if (!res.ok) {
            throw new Error('Failed to fetch data')
        }
        
        const data = await res.json()
        return <div>{data.content}</div>
    } catch (error) {
        return <div>加载失败</div>
    }
}

7.2 重试机制 #

tsx
async function fetchWithRetry(url: string, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const res = await fetch(url)
            if (res.ok) return res.json()
        } catch (error) {
            if (i === retries - 1) throw error
        }
    }
}

7.3 超时处理 #

tsx
async function fetchWithTimeout(url: string, timeout = 5000) {
    const controller = new AbortController()
    const timeoutId = setTimeout(() => controller.abort(), timeout)
    
    try {
        const res = await fetch(url, { signal: controller.signal })
        return res.json()
    } finally {
        clearTimeout(timeoutId)
    }
}

八、最佳实践 #

8.1 封装API请求 #

tsx
const API_BASE = process.env.NEXT_PUBLIC_API_URL

interface FetchOptions extends RequestInit {
    cache?: 'force-cache' | 'no-store'
    revalidate?: number
    tags?: string[]
}

export async function api<T>(
    endpoint: string,
    options: FetchOptions = {}
): Promise<T> {
    const { cache, revalidate, tags, ...fetchOptions } = options
    
    const url = `${API_BASE}${endpoint}`
    
    const res = await fetch(url, {
        ...fetchOptions,
        cache,
        next: revalidate || tags ? { revalidate, tags } : undefined,
    })
    
    if (!res.ok) {
        throw new Error(`API Error: ${res.status}`)
    }
    
    return res.json()
}

8.2 类型安全 #

tsx
interface User {
    id: string
    name: string
    email: string
}

interface Post {
    id: string
    title: string
    content: string
    author: User
}

export async function getUser(id: string): Promise<User> {
    return api<User>(`/users/${id}`)
}

export async function getPosts(): Promise<Post[]> {
    return api<Post[]>('/posts', { next: { revalidate: 60 } })
}

8.3 并行请求 #

tsx
export default async function Page() {
    const [users, posts, comments] = 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/comments').then(res => res.json()),
    ])
    
    return (
        <div>
            <UsersList users={users} />
            <PostsList posts={posts} />
            <CommentsList comments={comments} />
        </div>
    )
}

九、总结 #

数据获取基础要点:

要点 说明
fetch扩展 缓存和重新验证
缓存选项 force-cache/no-store/revalidate
请求去重 自动去重相同请求
静态获取 构建时获取数据
动态获取 每次请求获取数据
ISR 增量静态再生

下一步,让我们学习Server Components数据获取!

最后更新:2026-03-28