Next.js
Performance
Cache
Arquitetura
React

Next.js 中的缓存和流式传输:性能成为架构决策

感知性能不再是最终的调整,而是成为一种架构选择,具有真正的收益和旧数据的风险。

Next.js 中的缓存和流式传输:性能成为架构决策

长期以来,表演是项目的最后阶段。应用程序已构建,最后进行了测量,当页面速度缓慢时,就会出现缓解措施:这里缓存,那里旋转器,那里延迟加载。这是一个最后的细节,是在做出重要决定之后处理的。

现代的[Next.js0 拆除了这个顺序。缓存、流媒体和 Suspense 不是您在最后打开的按钮;而是您在最后打开的按钮。它们是页面组装和交付方式的属性。决定何时重新计算数据、屏幕各部分显示的顺序以及在其余部分之前可以提供哪些服务都是结构选择。本文的论点很简单:感知性能成为一个架构决策,将其视为最终细节会变得昂贵。

解决不同问题的三种机制

将每个部分的作用分开是值得的,因为它们往往与“快速离开”的单一模糊概念相混淆。

缓存的目的是不重做工作。如果数据已被获取或页面已呈现,保存它可以避免在下一个请求时再次支付相同的成本。增益在于吞吐量和延迟:不需要接触存储体的响应可以在很短的时间内输出。

流媒体意味着无需等待一切准备就绪即可开始交付。服务器不会将整个页面保留到最慢的部分完成,而是在每个块可用时以块的形式发送 HTML。当其余的仍在组装时,用户开始查看已经到达的东西并与之交互。

悬念使得流媒体变得可用。它允许您在组件本身中声明“虽然此数据不够,但显示此数据”。它标记了已准备好的内容和仍在加载的内容之间的界限,使框架能够以连贯的块而不是单个块的形式发送屏幕。

他们如何协同工作

魔法就出现在组合中。想象一个产品页面:标题、项目数据、评论和推荐。标题和项目数据很快。评估依赖于大量聚合。建议使用缓慢的外部服务。

在旧模型中,整个页面等待最慢的组件。用户看着空白的屏幕,直到一切都完成,包括来自脾气暴躁的第三方的推荐。该页面的性能受制于最糟糕的元素。

通过 Suspense 界定每个块和活动流,服务器可以立即提供项目标题和数据,并用加载指示器代替评级和推荐。当每个部件在服务器上准备就绪时,它就会被传输并卡入到位。在底层,缓存确保在下次访问时,未更改的部分甚至不需要重新计算。这三种机制加起来:缓存减少了工作量,流式传输消除了最坏情况下的等待,Suspense 组织了交付。

结果是,感知的性能停止取决于最慢的组件,而开始取决于您如何划定界限。绘制边界就是建筑。

为什么这成为一个架构决策,而不是最终调整

请注意,上面的每个选择都是在早期做出的,而不是在最后做出的。在哪里放置 Suspense 限制、哪些数据可以等待、哪些数据需要在第一个字节中、哪些数据可缓存以及缓存多长时间:所有这些都决定了组件结构和获取数据的方式。

您无法在作为一次性获取所有内容的整体块编写的页面上“稍后添加流”。要进行流式传输,页面必须设计为独立的部分,每个部分都有自己的加载边界。这种分解是在开始时与数据建模一起发生的设计决策。

缓存也是如此。实际上,决定可以从保存的版本中提供什么以及需要始终保持最新的内容就是根据过时的容忍度对域数据进行分类。这不是性能调整,而是关于业务的声明:这个价格可能是几分钟前的价格吗?这个平衡,不是吗?这些答案属于架构,那些推迟到最后的人会发现重写数据搜索结构比以前想到的要昂贵得多。 [Next.js1? App Router 指南] 与框架其余部分的关系更加清晰。

漂亮的幻灯片中没有人提到的风险:误解缓存

缓存是最诱人也是最危险的部分。 “缓存失效是计算中的难题之一”这句经典短语不是程序员的笑话,而是生产描述。

核心问题是旧数据。当您决定保存答案时,您就接受了当有人再次阅读该答案时该答案可能已过时。对于变化缓慢的内容来说,非常棒。对于余额、库存、订单状态来说,提供保存时间过长的版本意味着向用户展示不再存在的现实。这种错误最糟糕的类型是无声的:没有任何中断,没有任何错误,屏幕只是撒谎。

Next.js 提供了精细的缓存控制,正是因为这些决策需要针对每个数据,而不是全局的。但精细控制是一把双刃剑:不准确了解数据存储在哪一层的开发人员将会调试幻象行为。为什么这个页面不更新?因为有一个他忘记存在的缓存层,并且有一个无人触发的失效密钥。任何想要深入研究的人都可以在[应用程序中的良好缓存实践2]中找到很好的概述。

复杂性是交易的一部分

需要诚实地包括认知成本。通过多层缓存、流媒体和 Suspense 边界,“用户请求此页面时发生的情况”的心理模型变得更加丰富,并且更难以在您的头脑中保持。

数据可以存储在多个级别,具有不同的生命周期。页面的一部分在服务器上呈现并流式传输,另一部分在客户端上进行水合。当某些东西看起来已经过时时,调查需要遍历这些层来找出旧版本卡在哪里。这要求团队理解模型,而不仅仅是从互联网上的示例中复制配置。

因此,实际建议是明确且保守。从比看起来诱人的更少的缓存开始,并添加层作为测量保证,记录每个层的失效策略。将敏感数据的积极缓存视为需要合理性的决定,而不是默认情况。黄金法则是,没有人能够仅仅通过耸耸肩来解释为什么屏幕显示旧信息。

决策时应考虑哪些因素

缓存、流媒体和 Suspense 一起使感知性能比上一代的终结技巧要好得多。收益是具体的:分部分出现的页面、不重复的工作、在最坏的情况下不会减慢用户速度的等待。

相反,这些收益来自于早期做出的有关组件边界和对过时数据的容忍度的决策。将它们推迟到最后并不是中立的:它放弃流并将缓存推入静默的旧数据领域。感知的性能成为架构,这意味着规划和失效规则。

如果您正在定义应用程序的堆栈 Next.js,并且希望避免无人理解的缓存噩梦,那么值得在第一个屏幕之前设计数据策略。我已经讨论过这个设计很多次了;在评论或网络上找到我来交流想法。

另请阅读

  • [服务器优先:减轻浏览器负担的架构决策4
  • [Next.js App Router:默认思考服务器指南5
  • [Next.js 中的服务器操作:无需为此维护 API 的突变6
  • [什么是 React 服务器组件以及为什么逻辑要回归服务器7
  • [应用程序中的缓存8
  • [应用程序中的缓存:良好实践和基础知识9