很多团队排查Next.js缓存问题时,习惯把fetch当成一个黑盒。看到数据没更新,第一反应就是加上{ cache: 'no-store' },结果性能直接崩掉。问题的根源不在框架,而在概念:开发者把四个完全不同的缓存机制混为一谈。

这四个机制分别是:请求记忆化、数据缓存、完整路由缓存、路由缓存。它们的作用范围、生命周期、失效规则各不相同。搞混边界,就会写出看起来像框架bug、实际上是架构必然结果的代码。

打开网易新闻 查看精彩图片

四层缓存,各管各的

请求记忆化是渲染级别的优化。在单次React渲染过程中,如果多个组件请求同一个URL、同一组参数,Next.js只会真正执行一次fetch,然后把结果返回给所有调用方。它的作用域是渲染树,服务器把HTML发出去之后,这层缓存就重置了,下一次请求从零开始。

数据缓存是服务器端的持久化存储。fetch完成后,响应会按URL和参数存进服务端缓存,后续请求直接命中,不再走网络。它一直存在,直到你用revalidatePathrevalidateTag或者时间窗口主动让它失效。

完整路由缓存存的是构建时预渲染的静态HTML,命中后完全跳过服务端渲染。路由缓存则活在客户端,记住用户导航过的路由负载,让前进后退更快。

记忆化是临时的,数据缓存是持久的

请求记忆化夹在组件和数据缓存之间。组件调用fetch时,Next.js先查记忆化缓存,命中就立刻返回;没命中才去查数据缓存;数据缓存也没有,才真正发起网络请求,然后把结果同时写进两层缓存。

这个区别很关键。记忆化只活在渲染期间,渲染结束就没了。数据缓存则跨请求存活,直到被显式失效。把两者搞混的人,会把失效策略用在错误的层上,然后困惑为什么缓存清不掉。

数据缓存对App Router里的GET请求默认开启。不加任何配置,fetch会一直缓存;加上{ next: { revalidate: 3600 } },每小时后台重新拉取一次;加上{ cache: 'no-store' },则完全绕过数据缓存。

失效是手动的,别指望自动

缓存的存储位置取决于部署方式。在Vercel上,它是跨所有Serverless函数调用的分布式缓存;自托管时,它是Node.js进程本地的内存或文件缓存。生产环境下,缓存能扛住服务器重启。

失效必须手动触发。调用revalidatePath('/products')会清掉该路由关联的所有数据缓存条目;调用revalidateTag('products')会清掉打了这个标签的条目;时间窗口失效则是在后台重新拉取,同时先把旧缓存返回给用户。

把四层机制分开理解之后,团队就不会再过度失效——白白浪费CPU,也不会失效不足——把过期数据端给用户。框架的行为变得可读、可预测。