nextjs学习9:数据获取fetch、缓存与重新验证

在 Next.js 中如何获取数据呢?

Next.js 优先推荐使用原生的 fetch 方法,因为 Next.js 拓展了原生的 fetch 方法,为其添加了缓存和更新缓存(重新验证)的机制。

这样做的好处在于可以自动复用请求数据,提高性能。坏处在于如果你不熟悉,经常会有一些"莫名奇妙"的状况出现......

服务端使用 fetch

Next.js 拓展了原生的 fetch Web API,可以为服务端的每个请求配置缓存(caching)和重新验证( revalidating)行为。

你可以在服务端组件、路由处理程序、Server Actions 中搭配 async/await 语法使用 fetch。

基本用法

js 复制代码
// app/page.js
async function getData() {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos')
  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>{JSON.stringify(data)}</main>
}

默认缓存

默认情况下,Next.js 会自动缓存服务端 fetch 请求的返回值(背后用的是数据缓存(Data Cache))。

js 复制代码
// fetch 的 cache 选项用于控制该请求的缓存行为
// 默认就是 'force-cache', 平时写的时候可以省略
fetch('https://...', { cache: 'force-cache' })

但这些情况默认不会自动缓存:

  1. 在 Server Action 中使用的时候
  2. 在定义了非 GET 方法的路由处理程序中使用的时候

简单的来说,在服务端组件和只有 GET 方法的路由处理程序中使用 fetch,返回结果会自动缓存。

logging 配置项

在写代码之前,先让我们修改下 next.config.mjs 的配置:

js 复制代码
const nextConfig = {
  logging: {
    fetches: {
      fullUrl: true
    }
  }
};

export default nextConfig;

目前 logging 只有这一个配置,用于在开发模式下显示 fetch 请求和缓存日志。

服务端组件使用

第一种在服务端组件中使用,修改 app/fetch/page.tsx,代码如下:

js 复制代码
async function getData() {
  // 接口每次调用都会返回一个随机的猫猫图片数据
  const res = await fetch('https://api.thecatapi.com/v1/images/search')
  if (!res.ok) {
    throw new Error('Failed to fetch data')
  }

  return res.json()
}

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

  return <img src={data[0].url} width="300" />
}

在开发模式下,为了方便调试,可以使用浏览器的硬刷新(Command + Shift + R)清除缓存,此时数据会发生更改(cache: SKIP)。普通刷新时因为会命中缓存(cache: HIT),数据会保持不变。

命中缓存时 6ms 就返回了。

不命中缓存,需要912ms。

运行 npm run build && npm run start 开启生产版本。因为 fetch 请求的返回结果被缓存了,无论是否硬刷新,图片数据都会保持不变。

路由处理程序 GET 请求

第二种在路由处理程序中使用,新建 app/api/cats/route.js,代码如下:

js 复制代码
export async function GET() {
  const res = await fetch('https://dog.ceo/api/breeds/image/random')
  
  const data = await res.json()
  return Response.json({ data })
}

开发模式下,浏览器硬刷新的时候会跳过缓存,普通刷新的时候则会命中缓存。可以看到第一次硬刷新的时候,请求接口时间为 5418ms,后面普通刷新的时候,因为使用缓存中的数据,数据返回时间都是 0ms 左右。

运行 npm run build && npm run start 开启生产版本,因为 fetch 请求的返回结果被缓存了,无论是否硬刷新,接口数据都会保持不变。

重新验证

在 Next.js 中,清除数据缓存并重新获取最新数据的过程就叫做重新验证(Revalidation)。

Next.js 提供了两种方式重新验证:

一种是基于时间的重新验证(Time-based revalidation) ,即经过一定时间并有新请求产生后重新验证数据,适用于不经常更改且新鲜度不那么重要的数据。

一种是按需重新验证(On-demand revalidation) ,根据事件手动重新验证数据。按需重新验证又可以使用基于标签(tag-based)和基于路径(path-based)两种方法重新验证数据。适用于需要尽快展示最新数据的场景。

基于时间的重新验证

使用基于时间的重新验证,你需要在使用 fetch 的时候设置 next.revalidate 选项(以秒为单位):

js 复制代码
fetch('https://...', { next: { revalidate: 3600 } })

或者通过路由段配置项进行配置,使用这种方法,它会重新验证该路由段所有的 fetch 请求。

那什么是路由段呢?

在这张图中,/dashboard/settings由三段组成:

  • /:根段(Root Segment)
  • dashboard:段(Segment)
  • settings:叶段(Leaf Segment)

路由段配置选项可以配置页面、布局、路由处理程序的行为。比如我们使用 fetch 的时候可以单独配置某个请求的 revalidate ,借助路由段配置,我们可以配置这个路由下所有 fetch 请求的 revalidate

所以可以这么设置:

js 复制代码
// layout.jsx | page.jsx | route.js
export const revalidate = 3600

按需重新验证

使用按需重新验证,在路由处理程序或者 Server Action 中通过路径( revalidatePath) 或缓存标签 revalidateTag 实现。

revalidatePath

新建 app/api/revalidatePath/route.js,代码如下:

js 复制代码
import { revalidatePath } from 'next/cache'
 
export async function GET(request) {
  const path = request.nextUrl.searchParams.get('path')
 
  if (path) {
    revalidatePath(path)
    return Response.json({ revalidated: true, now: Date.now() })
  }
 
  return Response.json({
    revalidated: false,
    now: Date.now(),
    message: 'Missing path to revalidate',
  })
}

在上面的例子中,访问/api/cats页面内容都不变的,因为被缓存了,现在我如果访问下/api/revalidatePath?path=/api/cats,因为这个接口里面有revalidatePath(path),所以会更新/api/cats这个接口的数据,当再次访问/api/cats时,内容就变了。

注意:在开发模式下,用 revalidatePath 确实更新了对应路径上的 fetch 缓存结果。但如果大家部署到生产版本,你是发现 revalidatePath 只对页面生效,对路由处理程序并不生效。

这是因为 /api/cats静态处理 了(不同于页面的静态渲染),静态处理表示响应内容在 npm run build 构建阶段生成并固化,部署后直接返回缓存的响应,无需实时计算。

首先你要将 /api/cats 转为动态处理(响应内容在用户每次请求时实时生成,不提前固化,每次请求都执行处理程序逻辑),然后才能测试 revalidatePath 的效果。

但是转为动态处理,比如使用 cookies 等函数,又会触发 Next.js 的自动逻辑,让 fetch 请求退出缓存。

简而言之,如果你想在生产环境测试 revalidatePath 对路由处理程序的影响,你需要多做一些配置:

js 复制代码
// 路由动态处理, 每次请求都会返回新的内容
export const revalidate = 0
// fetch 强制缓存,这里有进行了强制缓存
export const fetchCache = 'force-cache'

export async function GET() {
  const res = await fetch('https://dog.ceo/api/breeds/image/random')
  
  const data = await res.json()
  return Response.json({ data, now: Date.now() })
}
revalidateTag

Next.js 有一个路由标签系统,可以跨路由实现多个 fetch 请求重新验证。具体这个过程为:

  1. 使用 fetch 的时候,设置一个或者多个标签标记请求
  2. 调用 revalidateTag 方法重新验证该标签对应的所有请求
js 复制代码
// app/page.js
export default async function Page() {
  const res = await fetch('https://...', { next: { tags: ['collection'] } })
  const data = await res.json()
  // ...
}

在这个例子中,为 fetch 请求添加了一个 collection标签。在 Server Action 中调用 revalidateTag,就可以让所有带 collection 标签的 fetch 请求重新验证。

js 复制代码
// app/actions.js
'use server'
 
import { revalidateTag } from 'next/cache'
 
export default async function action() {
  revalidateTag('collection')
}

客户端使用路由处理程序

如果你需要在客户端组件中获取数据,可以在客户端调用路由处理程序。

路由处理程序会在服务端被执行,然后将数据返回给客户端,适用于不想暴露敏感信息给客户端(比如 API tokens)的场景。

如果你使用的是服务端组件,无须借助路由处理程序,直接获取数据即可。

相关推荐
陈佬昔没带相机3 小时前
2025年终总结:Vibe Coding 之后,胆儿肥了
ai编程·全栈·next.js
小p2 天前
nextjs学习8:静态渲染、动态渲染、Streaming渲染
next.js
小满zs7 天前
Next.js第二十章(MDX)
前端·next.js
小p7 天前
nextjs学习6:服务端组件和客户端组件
next.js
多啦C梦a8 天前
《双Token机制?》Next.js 双 Token 登录与无感刷新实战教程
前端·全栈·next.js
小p8 天前
nextjs学习4:创建服务端 API(路由处理程序)
next.js
小p8 天前
nextjs学习5:Suspense 与 Streaming
next.js
无敌暴龙战士通关前端9 天前
3天速成 使用AI《从零开发一款 AI 面试作弊助手》一
react.js·next.js