大部分对照文档看的,也加了一些自己的体会
请求记忆化 Request Memoization
使用 fetch API 自动 Memoization 相同 URL 和选项的请求,看下图就知道,在树的顶部获取数据,在内部组件也发出同样的请求,最终也只会发出一次。以前都是是通过 Props 传递数据 或者 Context
但是这样有限制条件
Request Memoization是 React 的特性,而不是 Next.js 的特性Request Memoization仅仅使用fetch请求的GET方法,因为这类数据通常不需要改变Request Memoization仅适用于 React 组件树, 这意味着- 它适用于
generateMetadata、generateStaticParams、Layouts、Pages 和其他服务器组件中的fetch请求 - 它不适用于
Route Handlers中的fetch请求,因为他们不是 React 组件树的一部分,这是因为Route Handlers运行在服务端,并不涉及 UI 渲染,而是直接处理 HTTP 请求并返回
- 它适用于
- 对于不适用的情况,可以使用 React cache 函数来处理
持续时间 Duration
缓存在服务器请求的生命周期内持续,直到 React 组件树渲染完成
重新验证 Revalidating
由于 memoization 不在服务器请求之间共享,并且仅在渲染期间应用,因此无需重新验证它
退出 Opting out
memoization 仅适用于 fetch 请求中的 GET 方法,其他方法 (如 POST 和 DELETE) 不会被记忆化。这是 React 的默认优化,我们不建议退出
如果有特殊的情况,可以使用来自 AbortController 的 signal 属性。但是,这不会使请求退出 memoization,而是中止正在进行的请求
const { signal } = new AbortController()
fetch(url, { signal })
数据缓存 Data Cache
Next.js 有一个内置的数据缓存,可以在 server requests 和 deployments 之间 persists 数据获取结果。这是可能的,因为 Next.js 扩展了原生的 fetch API,允许服务器上的每个请求设置自己的持久缓存语义
有意思的是,在浏览器中, fetch 的 cache 选项表示请求如何与浏览器的 HTTP 缓存交互,而在 Next.js 中,cache 选项表示服务器端请求如何与服务器的数据缓存交互。
我个人觉得这是因为在 Next.js 请求数据是服务器组件的行为,如果有复杂的交互除外
data cache 和 memoization 的区别
虽然这两种缓存机制都通过重用缓存数据来提高性能,但数据缓存在传入 requests 和 deployments 之间是持久的,而记忆化仅在请求的生命周期内持续
持续时间
除非你 revalidate 或者 opt-out,否则数据缓存在传入 requests 和 deployments 之间是持久的
重新验证
基于时间的重新验证
适用于不用经常变的数据
// 最多每小时重新验证一次
fetch('https://...', { next: { revalidate: 3600 } })
或者可以使用 Route Segment Config options 配置所有的 fetch请求
按需重新验证 On-demand Revalidation
数据通过 revalidatePath 和 revalidateTag 按需重新验证
退出 Opting out
let data = await fetch('https://api.vercel.app/blog', { cache: 'no-store' })
完整路由缓存 Full Route Cache
相关术语 Automatic Static Optimization 自动静态化 Static Site Generation 静态站点生成 Static Rendering 静态渲染
Next.js 在 build 阶段会自动渲染和缓存路由,这是一种优化手段,允许你提供缓存路由而不是每次请求时都在服务器上进行渲染,从而实现更快的页面加载
1、React在服务器上的渲染
在服务器上,Next.js 使用 React 的 API 来协调渲染。渲染工作被分成块:按个 individual routes 和 Suspense 边界。
每个块分两步渲染:
- React 将服务器组件渲染成一种特殊的数据格式,这种格式针对流式传输进行了优化,称为React Server Component Payload(RSC Payload)。
- Next.js 使用 RSC 和客户端组件 JavaScript 指令在服务器上渲染 HTML。
这意味着我们不必等待所有内容都渲染完成才能缓存工作或发送响应。相反,我们可以在工作完成时流式传输响应。
什么是 React Server Component Payload
RSC Payload 是已渲染的 React 服务器组件树的紧凑二进制(compact binary)表示。React 在客户端使用它来更新浏览器的 DOM。React 服务器组件负载包含:
- 服务器组件的渲染结果
- 客户端组件应该渲染的位置的占位符以及它们的 JavaScript 文件的引用
- 从服务器组件传递到客户端组件的所有 props
要了解更多,请参阅服务器组件文档。
2、Next.js 在服务器的缓存
Next.js 的默认行为是在服务器上缓存路由的渲染结果(React 服务器组件有效负载和 HTML)。这适用于构建时静态渲染的路由或在重新验证期间。
3、React 在客户端的 Hydration 和 Reconciliation
在请求时,在客户端:
1、HTML 用于立即显示客户端和服务器组件的快速非交互式初始预览 2、RSC Payload 用于 reconcile 客户端和一渲染的服务组件树,并且更新 DOM 3、 Javascript 指令用于 hydrate 客户端组件并且是应用程序具有交互性
4、Next.js 在可短的缓存(路由器缓存)
RSC Payload 存储在客户端 Router Cache,这是一个按 individual route 端分割的独立内存缓存。Router Cache 通过存储之前访问过的路由和 prefetching future routes 改善导航体验
5、后续导航Subsequent Navigations
在后续导航或 prefetching 期间,Next.js 将检查路由器缓存中是否存储了 React Server Component Payload。如果存在,它将跳过向服务器发送新请求。
如果路由端 route segment 不在缓存中,Next.js 将从服务器获取 RSC Payload,并在客户端填充路由器缓存
静态和动态渲染
路由是否在构建时缓存取决于它是静态渲染还是动态渲染。静态路由默认被缓存,而动态路由在请求时渲染,不被缓存
持续时间 Duration
Full Router Cache 是持久的
失效
有两种方式可以使 Full Router 缓存失效:
- Revalidating Data:重新验证数据缓存,将依次通过在服务器上重新渲染组件并缓存新的渲染输出来使路由器缓存失效。
- 重新部署:与跨部署持久化的数据缓存不同,完整路由缓存在新部署时被清除
退出 Opting out
你可以通过以下方式退出完整路由缓存,或者换句话说,为每个传入请求动态渲染组件:
- 使用动态 API:这将使路由退出完整路由缓存并在请求时动态渲染。数据缓存仍然可以使用。
- 使用
dynamic = 'force-dynamic'或revalidate = 0路由段配置选项:这将跳过完整路由缓存和数据缓存。这意味着组件将在每个传入服务器请求时渲染,数据将被获取。路由器缓存仍然适用,因为它是客户端缓存。 - 退出数据缓存:如果路由有一个未缓存的
fetch请求,这将使路由退出完整路由缓存。特定fetch请求的数据将为每个传入请求获取。其他未选择退出缓存的fetch请求将继续在数据缓存中缓存。这允许缓存数据和未缓存数据的混合。
客户端路由器缓存
Next.js 有一个内存中的客户端路由器缓存,用于存储按布局、加载状态和页面分割的路由段的 RSC 负载。
当用户在路由之间导航时,Next.js 会缓存访问过的路由段并预取用户可能导航到的路由。这会导致即时的后退/前进导航,在导航之间不需要完整页面刷新,并保留 React 状态和浏览器状态。
使用路由器缓存:
- 布局在导航时被缓存和重用(部分渲染)。
- 加载状态在导航时被缓存和重用,用于即时加载状态。
- 页面默认不缓存,但在浏览器后退和前进导航时会被重用。你可以使用实验性的
staleTimes配置选项来启用页面段的缓存。
值得注意的是:这个缓存专门适用于 Next.js 和服务器组件,与浏览器的 bfcache 不同,虽然它有类似的结果。
持续时间
缓存存储在浏览器的临时内存中。路由器缓存的持续时间由两个因素决定:
-
会话:缓存在导航过程中保持不变。但是,在页面刷新时会清除。
-
自动失效期:布局和加载状态的缓存会在特定时间后自动失效。持续时间取决于资源是如何预取的,以及资源是否被静态生成:
- 默认预取(
prefetch={null}或未指定):动态页面不缓存,静态页面缓存 5 分钟。 - 完整预取(
prefetch={true}或router.prefetch):静态和动态页面都缓存 5 分钟。
- 默认预取(
虽然页面刷新会清除所有缓存的段,但自动失效期只影响从预取时间开始的单个段。
值得注意的是:实验性的
staleTimes配置选项可以用来调整上述自动失效时间。
失效
有两种方式可以使路由器缓存失效:
-
在服务器操作中:
- 通过路径(
revalidatePath)或缓存标签(revalidateTag)按需重新验证数据 - 使用
cookies.set或cookies.delete使路由器缓存失效,以防止使用 cookies 的路由变得过时(例如身份验证)。
- 通过路径(
-
调用
router.refresh将使路由器缓存失效,并为当前路由向服务器发出新请求。
退出选项
从 Next.js 15 开始,页面段默认退出缓存。
值得注意的是:你也可以通过将
<Link>组件的prefetch属性设置为false来退出预取。
缓存交互
在配置不同的缓存机制时,理解它们之间如何交互很重要:
数据缓存和完整路由缓存
- 重新验证或退出数据缓存会使完整路由缓存失效,因为渲染输出依赖于数据。
- 使完整路由缓存失效或退出不会影响数据缓存。你可以动态渲染一个同时具有缓存数据和未缓存数据的路由。这在你的页面大部分使用缓存数据,但有几个组件依赖于需要在请求时获取的数据时很有用。你可以动态渲染而不用担心重新获取所有数据对性能的影响。
数据缓存和客户端路由器缓存
- 要立即使数据缓存和路由器缓存失效,你可以在服务器操作中使用
revalidatePath或revalidateTag。 - 在路由处理程序中重新验证数据缓存不会立即使路由器缓存失效,因为路由处理程序不与特定路由关联。这意味着路由器缓存将继续提供之前的负载,直到强制刷新或自动失效期已过。