🍂落页
登 录

Next.js 笔记

  • 安装及项目结构
  • 路由
  • 获取数据
  • 渲染
  • 缓存
  • 优化
🍂落页
TALAXY
缓存
🍂落页
TALAXY

缓存

缓存
  • 请求记忆化
  • 数据缓存
  • 全路由缓存
  • 路由缓存
  • 不同缓存间的交互
  • 相关 API
  • 参考

默认情况下,路由是静态渲染的,同时数据请求会被缓存。

如下图,一个路由在 构建时 和 请求时 会有不同的缓存行为:

  • 构建时 - 会重新设置全路由缓存 TODO
  • 请求时 - 会先尝试客户端的路由缓存,若未命中则会向服务器重新请求。

请求记忆化

React 扩展了 fetch API ,会自动记忆相同的 URL 及选项参数的请求结果。这意味着在 React 组件树中不同的位置去发起相同的请求,且这样的请求只会执行一次。

async function getItem() {
    const res = await fetch('https://.../item/1');
    return res.json();
}

const item = await getItem();   // 未命中缓存 
const item = await getItem();   // 命中缓存
  • 请求记忆化是 React 的特性,而非 Next.js ;
  • 只会对 fetch 的 GET 请求进行记忆化;
  • 记忆化只会用在 React 组件树上,意味着:
    • 会应用在 generateMetadata、generateStaticParmas、layout、page 和一些其他的服务端组件;
    • 不会应用在 Router Handler 中的 fetch 请求。
  • 一些无法用 fetch 获取的数据(比如数据库、GraphQL 等),可以用 React 的 cache 函数来进行记忆化。

  • 缓存的生命周期:从服务器接收到请求,直到 React 组件树被渲染完;

  • 记忆化的数据不会在不同请求中共享,并只用于渲染,所以无需重新校验数据;

  • 可以将 AbortController 的 signal 传给 fetch 来取消记忆化的行为。

    const { signal } = new AbortController();
    fetch(url, { signal });
    

数据缓存

Next.js 扩展了 fetch API 来进行数据缓存,它会对来自服务器上(包括部署)的数据请求结果进行持久化。

默认情况下 fetch 会进行数据缓存。可以用 fetch 中的 cache 或 next.revalidate 选项来自定义缓存行为。

浏览器中 fetch 的 cache 选项参数用于控制浏览器中的 HTTP 缓存;在 Next.js 中,cache 用于设置服务器上数据缓存行为。

数据缓存是持久的,它不仅会应用于服务器发出的请求,还会应用于新的服务器部署过程中。

请求记忆化与数据缓存的区别:

  • 请求记忆化中缓存的生命周期为单次(页面)请求;数据缓存是持久的。
  • 请求记忆化旨在避免渲染时对同一接口的重复调用;数据缓存旨在减少对数据源的请求次数。

基于时间的重新校验

对缓存数据设定过期时间。通常用于变更频繁但实时性并不太重要的数据:

// 每一小时请求一次
fetch('https://...', { next: { revalidate: 3600 } });

或者也可以用路由片段配置来设置一个分段中所有 fetch 请求的配置。

按需的重新校验

可以通过路径(revalidatePath())或缓存标签(revalidateTag())来踢除缓存:

fetch('...', { next: { tags: ['a'] } });    // 未命中,请求后会缓存结果,并标记为 'a'
revalidateTag('a');                         // 踢除了所有带有 'a' 标签的缓存
fetch('...', { next: { tags: ['a'] } });    // 仍未命中,请求后会缓存结果,并标记为 'a'

不使用数据缓存

对于个人数据,可以将 cache 设为 no-store 来取消缓存行为:

fetch('...', { cache: 'no-store' });

或者用路由片段配置:

// 会对所有请求起效,包括第三方库
export const dynamic = 'force-dynamic'

全路由缓存

Next.js 会在构建时对路由进行渲染及缓存。通过缓存,每次请求时就不必重新渲染页面。

  1. 在服务端,React 会根据路由片段和 Suspense 来拆分代码。通过流式渲染,可以不必等待整个页面完全渲染,而是一小块一小块地将页面内容(RSC Payload)发送给客户端。
  2. Next.js 会将上述的 RSC Payload 缓存起来,不管是构建时还是重新校验数据时。
  3. 在客户端上也会对 RSC Payload 进行缓存,称为 Router Cache ,它是一个根据路由片段划分的内存缓存。在后续的导航操作中,会先检查是否有对应的路由缓存,如果有则不再向服务端请求。

静态路由默认会有全路由缓存;而动态路由不会有全路由缓存,而是每次请求时重新渲染。


  • 全路由缓存是持久的。
  • 使全路由缓存失效的方法:
    • 重新检验数据缓存;
    • 重新进行项目部署(每次项目部署都会清除之前的额全路由缓存)。
  • 取消全路由缓存的途径:
    • 使用一个动态函数,如 cookies() ;
    • 设置路由片段配置:dynamic = 'force-dynamic' 或 revalidate = 0 ;
    • 不使用数据缓存。对于 fetch ,如果没有数据缓存则也不会进行全路由缓存。

路由缓存

在客户端中,Next.js 会在用户会话期间对 RSC Payload 进行缓存,同时是根据路由片段区分。路由缓存是记忆在浏览器内存中的。

路由缓存对于用户有这些体验提升:

  • 立即响应的前进/后退导航切换 - 对于已访问的路由会直接使用路由缓存,而对于新路由会进行预先获取和部分渲染;
  • 导航行为不会导致整页加载,同时 React 和浏览器状态都会得到保留。

路由缓存和全路由缓存的区别:

  • 路由缓存仅在用户会话期间暂时保存,而全路由缓存在服务器上是持久的;
  • 全路由缓存只能缓存静态路由,而路由缓存均可应用在动态静态路由上。

  • 两个因素决定了路由缓存的生命周期:
    • 会话 - 缓存会持久于导航行为中,而当页面刷新时缓存会一并清空。
    • 自动过期 - 动态路由会在半分钟后过期,静态路由会在 5 分钟后过期。也可用 prefetch={true} 或 router.prefetch 来使动态路由的过期时间变为 5 分钟。
  • 使路由缓存失效的方法:
    • (在 Server Action 中)重新校验数据缓存,或者使用 cookies.set 或 cookies.get 方法;
    • 调用 router.refresh 向服务端重新请求当前路由。
  • 没有办法能禁用路由缓存。

不同缓存间的交互

  • 重新校验或者禁用数据缓存会导致全路由缓存失效,因为渲染内容的输出依赖于数据;
  • 在 Route Handler 中重新校验了数据缓存并不会使路由缓存失效。

相关 API

这张表 列出了所有控制缓存行为的 API 。

<Link>

默认情况下,<Link> 会自动预请求全路由缓存,并将 RSC Payload 添加到客户端的路由缓存中。

可以设置 prefetch={false} 来取消自动预请求的行为,但不会影响访问路由后的路由缓存行为。

useRouter

  • router.prefetch - 手动预请求路由,并将 RSC Payload 添加到客户端的路由缓存中。
  • router.refresh - 清除当前路由的路由缓存,并重新获取。会保留 React 和浏览器状态。

fetch

fetch 结果会自动添加到在数据缓存中:

// `force-cache` 是默认选项
fetch("https://...", { cache: 'force-cache' });

如果使用了 cache: 'no-store' 会跳过数据缓存,同时也会跳过全路由缓存。

动态函数

cookies、headers、searchParams 的时候会让路由动态渲染,因此不会有全路由缓存的行为。

在 Server Action 中使用 cookies.set 或 cookies.delete 会使路由缓存失效。

路由片段配置

当你不能使用 fetch 时(比如使用第三方库),可以通过路由片段设置来取消数据缓存及全路由缓存:

  • const dynamic = 'force-dynamic'
  • const revalidate = 0

generateStaticParams

对于动态片段(比如 app/blog/[slug]/page.js)可以提供一个 generateStaticParams ,构建时会根据给定参数生成全路由缓存;在请求阶段时遇到的新路由也会做缓存。

如果要拒绝请求阶段遇到的新参数,可用 const dynamicParams = false 配置,当遇到新参数时会返回 404 。

React cache

如果不使用 fetch 请求数据,也可用 React 的 cache 函数来实现请求记忆化:

import { cache } from 'react'
import db from '@/lib/db'
 
export const getItem = cache(async (id) => {
    const item = await db.item.findUnique({ id });
    return item;
})

参考

Caching in Next.js - nextjs.org

TALAXY 落于 2023年12月28日 。