默认情况下,路由是静态渲染的,同时数据请求会被缓存。
如下图,一个路由在 构建时 和 请求时 会有不同的缓存行为:

- 构建时 - 会重新设置全路由缓存 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 会在构建时对路由进行渲染及缓存。通过缓存,每次请求时就不必重新渲染页面。
- 在服务端,React 会根据路由片段和 Suspense 来拆分代码。通过流式渲染,可以不必等待整个页面完全渲染,而是一小块一小块地将页面内容(RSC Payload)发送给客户端。
 - Next.js 会将上述的 RSC Payload 缓存起来,不管是构建时还是重新校验数据时。
 - 在客户端上也会对 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向服务端重新请求当前路由。 
 - (在 Server Action 中)重新校验数据缓存,或者使用 
 - 没有办法能禁用路由缓存。
 
不同缓存间的交互
- 重新校验或者禁用数据缓存会导致全路由缓存失效,因为渲染内容的输出依赖于数据;
 - 在 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;
})