Next.js 疾速客户端导航,强势回归!

35 阅读9分钟

Sam Selikoff 质疑 Next.js RSC 缺乏灵敏导航。Next.js 通过 Cache Components 解决,利用部分预渲染和预加载,实现即时客户端导航,恢复 React 优势。

译自:How Next.js Got Its Snappy Client Navs Back

作者:Loraine Lawson

前端开发者 Sam Selikoff 近期加入 Vercel 担任资深工程师,他对 Next.js 团队提出了一个迫切的问题:为什么开发者在使用 React Server Components (RSC) 时无法获得灵敏的导航体验?

“我喜欢 App Router 的开发者体验(DX),Server Components 是 React 的一个很棒的补充,尤其是在获取数据和将其渲染到页面上时,”他在 10 月 22 日举行的 Next.js 大会上告诉听众,“我只是不明白为什么使用 RSC 就意味着我们必须放弃在 React 应用中习以为常的灵敏客户端导航。我们喜欢 React 的原因之一不就是它能够在浏览器中直接运行代码吗?”

他想知道,为什么 Next.js 不能将 RSC 用于初始页面渲染,而其他一切仍然使用传统的客户端 React 呢?

这是一个关于该框架如何回答他问题的故事。

网站再次走向服务端

Selikoff 将 Next.js 大会的听众带回 2023 年,当时 App Router 首次创建。他表示,那时前端框架将所有代码打包在服务器上,然后发送到浏览器,由浏览器进行数据获取和路由渲染——有点像 iOS 应用。

但网站不是 iOS。

“人们希望能够即时打开文章和推文链接,这正是为什么在 2012 年,Twitter 的工程团队在这篇帖子中分享说,他们通过将大量客户端渲染移回服务器,将初始加载时间缩短了 80%,”他说,“他们还将代码拆分成更小的包,以便在需要时进行懒加载。”

这导致框架添加了服务端渲染和动态导入等功能。

2017 年,Netflix 分享说,他们通过将更多的渲染和数据获取推到服务器端,并让预渲染的 HTML 完成繁重工作,从而从其着陆页中移除了所有客户端 React。据 Selikoff 称,Netflix 页面性能提升了 50%。

“所以,我们再次看到这个趋势在继续,更多的 API 在服务器端更早地完成更多工作,”他说。

Selikoff 说,当涉及到单页应用(SPA)导航时,完全依赖客户端代码处理路由问题的团队也遇到了性能瓶颈。

“2018 年,Square 的工程师撰文介绍了使用一种名为 engines 的新 Ember 功能,将他们的仪表盘拆分成可进行懒加载的各个部分,”他说。

LinkedIn 也采用了 Ember.js engines 的这一功能,因为他们网站上大量的 URL 导致了加载整个客户端的性能问题,他补充道。Remix 最近也添加了一项功能,以帮助 Shopify 解决同样的问题,他补充说。

“我们从经验中得知,以客户端为中心的方法是一个死胡同。这就是为什么 App Router 默认使用 Server Components 在服务器端渲染数据获取和路由。”

— Sam Selikoff, Vercel

“我提出所有这些观点是为了说明,经过十多年使用这些丰富前端的构建经验以及整个框架生态系统的发展,这就是当时的尖端技术,”他说,“我们拥有这些混合框架,它们最初是客户端优先的 SPA,但它们不断添加服务端功能,当你不可避免地遇到客户端的局限性时,你可以选择启用这些功能。”

App Router 登场

Selikoff 说,App Router 的目标是从根本上解决这些问题。

“我们从经验中得知,以客户端为中心的方法是一个死胡同,”他说,“这就是为什么 App Router 默认使用 Server Components 在服务器端渲染数据获取和路由。”

他表示,Next.js 希望将所有这些来之不易的经验融入框架中,以期永远不会触及这些性能瓶颈。

尽管如此,Selikoff 仍然想要他的灵敏客户端导航;他喜欢打开 iPhone,看到预渲染的屏幕在那里等待。

“我们难道不应该有这种选项来预渲染某些屏幕或预加载即将到来的客户端导航,以便提供最佳的用户体验吗?”他问道,“我想我们应该有。而且并非巧合,团队的其他成员也这么认为,因为这正是他们一直在努力的方向。”

介绍 Cache Components

团队表示,Cache Components 经过两年的开发,是一套新的可选功能,旨在使 Next.js 中的缓存更加明确和灵活。Selikoff 说,Cache Components 让开发者可以预渲染和预加载 UI,从而为 App Router 带来即时导航。

Cache Components 利用了一个新的“use cache”指令,该指令在使用时会通过编译器自动生成缓存键。它可以部署缓存页面、组件和函数。

Cache Components 默认在请求时执行任何页面、布局或 API 路由中的动态代码。他演示了这些新功能,展示了如何在具有实时比分的足球网站上使用它。

“如果你以前用过 Next,你会知道如果你能将路由设置为静态,通常会很不错,它通常会带来极快的用户体验,这很棒,”Selikoff 说,“但今天,当你在 Next 中将路由设置为静态时,你根本无法在初始请求期间获取任何动态内容。所以这是一个非此即彼的决定。如果你今天预渲染一个路由,你必须预渲染整个页面。”

“这就是使用 Next.js Cache Components 的体验。它默认是真正动态的,所以不再有隐式缓存。我们不需要在页面中添加 force dynamic,也不需要在 fetch 调用中添加 cache no store。”

Selikoff

但是,由于每个页面都有动态内容,开发者需要添加一些客户端数据获取逻辑或一些新的库,这些库在初始请求返回后才会完成。这将在客户端和服务器之间来回产生瀑布式请求,他说,所以开发者通常必须设置 API 端点。

“我们在这里什么都没做:这个页面既不是动态的也不是静态的,”他说,“它是两者兼而有之。”

Selikoff 说,相反,它是部分预渲染的,因此当 Cache Components 启用时,开发者无需在静态或动态之间进行选择。

“每个路由都是部分预渲染的,”他说,“页面大部分内容是动态的。它让浏览器立即启动应用,并且完全不会减慢动态数据的速度,这要归功于 React 对服务端流式传输的使用。”

他展示了如何使用 Suspense 内部的 wrap 来为 UI 的一部分创建骨架屏

“一旦 fetch 调用完成,数据就会流式传入,轻而易举,对吧?”他说,“这就是使用 Next.js Cache Components 的体验。它默认是真正动态的,所以不再有隐式缓存。我们不需要在页面中添加 force dynamic,也不需要在 fetch 调用中添加 cache no store。”

“只要我们在 Suspense 边界内,我们就可以毫无意外地获取数据。这些 Suspense 边界确保我们应用中的每个页面都能即时提供其静态内容,并尽可能早地为浏览器准备动态内容。”

灵敏的导航又回来了

这又回到了 Selikoff 对灵敏导航的请求。他提到了他的演示,那是一个游戏页面。

“假设我们刚刷新了这里,然后我们想回到‘游戏’页面,所以我们会看到,我们一点击,就立即导航到预渲染页面,然后内容填充进来。这是如何工作的?”他说,“答案是预加载。由于部分预渲染,链接将默认预加载即将到来的路由的静态内容。”

Selikoff 说,新的编程模型确保每个路由都有一些静态内容,因此获取成本低廉,并且在用户实际点击链接时不会过时。

他说,链接标签可以提前预加载,开发者可以重新获得灵敏的客户端导航。

“尽管最初涉及服务器,但当我们进行导航时,就好像您在客户端进行客户端导航一样,”他说,“我们能够利用浏览器获得灵敏的导航,而无需做任何额外工作。”

“我们不需要将任何数据获取代码移到客户端。我们不需要根据是初始渲染还是客户端导航来分叉我们的页面组件,我们也不需要创建任何特殊的 loading .TSX 文件。我们所要做的就是像往常一样使用 Suspense 和 RSC,Next 免费为我们提供了即时客户端导航。”

他还告诉听众,为什么这个功能被称为 Cache Components,尽管它似乎与 use cache 指令没有任何关系。

“如果你考虑我们到目前为止的网站,是什么让我们能够提前预加载这些页面中的静态内容?”Selikoff 问道,“正是因为我们知道这些静态内容不会过时。这就是我们能够预加载它的原因……这些静态内容不会改变,因为它基于我们在应用程序中编写的代码。除非我们更改代码并重新部署应用程序,否则它不会改变。所以这些静态的、预渲染的内容实际上就是缓存内容。”

他补充说,缓存不仅仅是“对我们静态渲染 API 的更新”。

如果开发者希望在用户点击应用后预加载依赖于这些内容的页面怎么办?他说,为此,开发者仍然会使用缓存。

“仅仅因为 App Router 是服务端优先的,并不意味着我们必须放弃那些最初让我们爱上 React 的交互方式,”Selikoff 说,“预加载是一个强有力的例子,说明还有更多、更多的方式。现在我们有了一种避免过去性能瓶颈的架构,我们知道这种新模型可以带我们走得比旧模型更远。”