数据获取、缓存与重新验证

数据获取是任何应用的核心部分。本文将介绍如何在 React 和 Next.js 中获取、缓存及重新验证数据。

数据获取有以下四种方式:

  1. 在服务端使用 fetch
  2. 在服务端使用第三方库
  3. 在客户端通过路由处理器获取
  4. 在客户端使用第三方库获取

在服务端使用 fetch 获取数据

Next.js 扩展了原生的 fetch Web API,允许您为服务端的每个 fetch 请求配置缓存重新验证行为。React 也扩展了 fetch,在渲染 React 组件树时自动记忆化 fetch 请求。

您可以在服务端组件、路由处理器服务端操作中使用 async/await 配合 fetch

例如:

async function getData() {
  const res = await fetch('https://api.example.com/...')
  // 返回值不会被序列化
  // 可以返回 Date、Map、Set 等类型

  if (!res.ok) {
    // 这会触发最近的 `error.js` 错误边界
    throw new Error('Failed to fetch data')
  }

  return res.json()
}

export default async function Page() {
  const data = await getData()

  return <main></main>
}

须知

  • Next.js 提供了在服务端组件中获取数据时可能需要的有用函数,如 cookiesheaders。这些函数会导致路由动态渲染,因为它们依赖于请求时的信息。
  • 在路由处理器中,fetch 请求不会被记忆化,因为路由处理器不属于 React 组件树。
  • 服务端操作中,fetch 请求不会被缓存(默认为 cache: no-store)。
  • 在 TypeScript 中使用 async/await 的服务端组件需要 TypeScript 5.1.3 或更高版本以及 @types/react 18.2.8 或更高版本。

数据缓存

缓存存储数据,避免每次请求都从数据源重新获取。

默认情况下,Next.js 会自动在服务端的数据缓存中缓存 fetch 的返回值。这意味着数据可以在构建时或请求时获取、缓存,并在每次数据请求时重复使用。

// 'force-cache' 是默认值,可以省略
fetch('https://...', { cache: 'force-cache' })

但也有例外情况,fetch 请求不会被缓存:

什么是数据缓存?

数据缓存是一个持久的 HTTP 缓存。根据您的平台,缓存可以自动扩展并跨多个区域共享

了解更多关于数据缓存的信息。

重新验证数据

重新验证是清除数据缓存并重新获取最新数据的过程。这在数据发生变化且您希望确保显示最新信息时非常有用。

缓存数据可以通过两种方式重新验证:

  • 基于时间的重新验证:在一定时间间隔后自动重新验证数据。适用于变化不频繁且实时性要求不高的数据。
  • 按需重新验证:根据事件(如表单提交)手动重新验证数据。按需重新验证可以使用基于标签或路径的方法一次性重新验证一组数据。适用于需要尽快显示最新数据的情况(例如无头 CMS 的内容更新时)。

基于时间的重新验证

要按时间间隔重新验证数据,可以使用 fetchnext.revalidate 选项设置资源的缓存生命周期(以秒为单位)。

fetch('https://...', { next: { revalidate: 3600 } })

或者,要重新验证路由段中的所有 fetch 请求,可以使用路由段配置选项

layout.js | page.js
export const revalidate = 3600 // 最多每小时重新验证一次

如果在静态渲染的路由中有多个 fetch 请求,且每个请求有不同的重新验证频率,则所有请求将使用最短的时间。对于动态渲染的路由,每个 fetch 请求将独立重新验证。

了解更多关于基于时间的重新验证

按需重新验证

可以在服务端操作路由处理器中通过路径 (revalidatePath) 或缓存标签 (revalidateTag) 按需重新验证数据。

Next.js 有一个缓存标签系统,用于跨路由使 fetch 请求失效。

  1. 使用 fetch 时,可以选择用一个或多个标签标记缓存条目。
  2. 然后可以调用 revalidateTag 重新验证与该标签关联的所有条目。

例如,以下 fetch 请求添加了缓存标签 collection

export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['collection'] } })
  const data = await res.json()
  // ...
}

然后可以在服务端操作中调用 revalidateTag 重新验证标记为 collectionfetch 调用:

'use server'

import { revalidateTag } from 'next/cache'

export default async function action() {
  revalidateTag('collection')
}

了解更多关于按需重新验证

错误处理与重新验证

如果在尝试重新验证数据时抛出错误,缓存将继续提供最后成功生成的数据。在后续请求中,Next.js 会重试重新验证数据。

退出数据缓存

fetch 请求在以下情况下不会被缓存:

  • fetch 请求中添加了 cache: 'no-store'
  • 单个 fetch 请求中添加了 revalidate: 0 选项。
  • fetch 请求位于使用 POST 方法的路由处理器中。
  • fetch 请求在使用了 headerscookies 之后。
  • 使用了 const dynamic = 'force-dynamic' 路由段选项。
  • fetchCache 路由段选项配置为默认跳过缓存。
  • fetch 请求使用了 AuthorizationCookie 标头,并且组件树中有未缓存的请求在其上方。

单个 fetch 请求

要为单个 fetch 请求退出缓存,可以将 fetchcache 选项设置为 'no-store'。这将在每次请求时动态获取数据。

layout.js | page.js
fetch('https://...', { cache: 'no-store' })

查看所有可用的 cache 选项,请参阅 fetch API 参考

多个 fetch 请求

如果在路由段(如布局或页面)中有多个 fetch 请求,可以使用路由段配置选项配置该段中所有数据请求的缓存行为。

但我们建议单独配置每个 fetch 请求的缓存行为,这样可以更精细地控制缓存行为。

在服务端使用第三方库获取数据

如果您使用的第三方库不支持或不暴露 fetch(例如数据库、CMS 或 ORM 客户端),可以使用路由段配置选项和 React 的 cache 函数配置这些请求的缓存和重新验证行为。

数据是否缓存取决于路由段是静态还是动态渲染。如果路由段是静态的(默认),请求的输出将作为路由段的一部分被缓存和重新验证。如果路由段是动态的,请求的输出将不会被缓存,并在每次渲染段时重新获取。

您还可以使用实验性的 unstable_cache API

示例

在以下示例中:

  • React 的 cache 函数用于记忆化数据请求。
  • 布局和页面段中的 revalidate 选项设置为 3600,意味着数据最多每小时缓存并重新验证一次。
import { cache } from 'react'

export const getItem = cache(async (id: string) => {
  const item = await db.item.findUnique({ id })
  return item
})

尽管 getItem 函数被调用了两次,但只会向数据库发送一次查询。

import { getItem } from '@/utils/get-item'

export const revalidate = 3600 // 最多每小时重新验证一次数据

export default async function Layout({
  params: { id },
}: {
  params: { id: string }
}) {
  const item = await getItem(id)
  // ...
}

在客户端通过路由处理器获取数据

如果需要在客户端组件中获取数据,可以从客户端调用路由处理器。路由处理器在服务端执行并将数据返回给客户端。这在您不想向客户端暴露敏感信息(如 API 令牌)时非常有用。

查看路由处理器文档以获取示例。

服务端组件与路由处理器

由于服务端组件在服务端渲染,您不需要从服务端组件调用路由处理器来获取数据。相反,可以直接在服务端组件中获取数据。

在客户端使用第三方库获取数据

您还可以使用第三方库(如 SWRTanStack Query)在客户端获取数据。这些库提供了自己的 API 用于记忆化请求、缓存、重新验证和变更数据。

未来 API

use 是一个 React 函数,接受并处理函数返回的 Promise。目前不建议在客户端组件中包装 fetch 使用 use,可能会触发多次重新渲染。了解更多关于 use 的信息,请参阅 React 文档