前端性能优化往往充满挑战。即使在高度优化的应用中,客户端-服务器瀑布流仍是最常见的性能瓶颈。在设计 Next.js App Router 时,我们就决心解决这个问题。为此,我们需要通过 React 服务端组件 (Server Components) 将客户端-服务器的 REST 请求转移到服务端,实现单次往返通信。这意味着服务器有时需要动态响应,牺牲了 Jamstack 出色的初始加载性能。我们为此开发了部分预渲染 (partial prerendering) 技术,实现了鱼与熊掌兼得的效果。
然而在这个过程中,我们提供的缓存默认值和控制机制影响了开发者体验。fetch()
的默认行为改为优先性能而默认缓存,但这给快速原型开发和高动态性应用带来了困扰。对于不使用 fetch()
的本地数据库访问,我们也没有提供足够的控制手段。虽然提供了 unstable_cache()
,但其易用性欠佳。这最终催生了分段级配置(如 export const dynamic, runtime, fetchCache, dynamicParams, revalidate = ...
)作为应急方案。
当然,我们会继续支持这些方案以保持向后兼容。但现在,请暂时忘记这些复杂机制——我们有了更简洁的新思路。
我们正在试验一种基于两个核心理念的新模式:<Suspense>
和 use cache
。
选择你的开发模式
首先你会注意到,当在组件中添加数据获取时,现在会触发错误提示:
对于数据、cookies、headers、当前时间或随机值的使用,你现在需要做出选择:希望数据被缓存(服务端或客户端)还是每次请求都重新执行?这里以 fetch()
为例,但该规则适用于任何异步 Node API,如数据库或定时器。
动态模式
如果你正在迭代开发或构建高动态性的仪表板,可以用 <Suspense>
边界包裹组件。<Suspense>
会启用动态数据获取和流式传输。
你也可以在根布局中使用此方案,或直接使用 loading.tsx
。
这能确保应用外壳保持即时响应。你可以继续在页面中添加更多数据,默认情况下它们都会是动态获取的。默认情况下不会进行任何缓存,彻底告别隐藏缓存问题。
静态模式
如果要构建静态内容且不需要动态功能,可以使用新的 use cache
指令:
通过标记 use cache
,你声明整个路由段应该被缓存。这意味着所有数据获取都可以被缓存,使页面能够静态渲染。静态内容不需要使用 <Suspense>
边界。在页面中添加更多数据时,它们都会被自动缓存。
混合模式
你还可以混合使用这两种模式。例如在根布局中使用 use cache
确保其被缓存,而每个布局或页面可以独立设置缓存策略。
同时在特定页面中使用动态数据:
缓存函数
使用这种混合模式时,将缓存逻辑贴近 API 调用可能更方便。
你可以像使用 use server
那样,在任何异步函数中添加 use cache
。可以将其视为服务端动作 (Server Action),但不是调用服务端而是调用缓存。它支持同样丰富的参数类型和超出 JSON 范围的返回值。缓存键会自动包含所有参数和闭包,无需手动指定。
由于该布局没有使用其他数据,可以保持静态。这种方式的优势在于:如果你意外在布局中添加了新的动态数据,构建时会触发错误,迫使你做出明确选择。如果在整个布局中添加 use cache
,则会被无错误地缓存。具体选择哪种方式取决于你的使用场景。
缓存标签
如果需要通过标签显式清除缓存条目,可以在 use cache
函数中使用新的 cacheTag()
API:
然后像之前一样,在服务端动作中调用 revalidateTag('my-tag')
。
由于该 API 可以在数据加载后调用,现在你可以使用数据来标记缓存条目:
定义缓存生命周期
如果需要控制特定条目或页面在缓存中的存活时间,可以使用 cacheLife()
API:
默认支持以下时间单位:
"seconds"
(秒)"minutes"
(分钟)"hours"
(小时)"days"
(天)"weeks"
(周)"max"
(最大值)
选择最适合你使用场景的粗略范围即可,无需精确计算一周有多少秒(或是毫秒?)。当然,你也可以指定具体值或配置自定义的缓存策略。
除了 revalidate
,该 API 还可以控制客户端缓存的过期时间 (stale
) 以及 expire
时间(决定页面在长时间未被访问时的过期时间)。
实验性功能
这目前仍是一个实验性项目,尚未达到生产就绪状态,仍存在功能缺失和错误。特别是我们知道需要改进这类新错误的错误堆栈。但如果你勇于尝试,我们非常期待你的早期反馈。
我们将发布更详细的升级路径。除了早期的错误提示外,这里主要的破坏性变更是取消了 fetch()
的默认缓存行为。因此我们建议目前仅在全新项目中进行实验。如果进展顺利,我们希望在次要版本中推出可选版本,并在未来的主要版本中设为默认。
要体验这些功能,你需要使用 Next.js 的 canary
版本:
同时需要在 next.config.ts
中启用 experimental.dynamicIO 标志: