译:PPR 是岛屿架构吗?

原文:https://zenn.dev/akfm/articles/ppr-vs-islands-architecture
作者:akfm_sato
译者:ChatGPT 4 Turbo

最近,我发表了一篇关于 Next.js 新渲染模型 Partial Pre-Rendering(以下简称 PPR)的文章。

https://zenn.dev/akfm/articles/nextjs-partial-pre-rendering

撰写这篇文章时,我没有特别意识到这一点,但在反馈中偶尔会提到岛屿架构。在公司内部讨论上述文章时,也类似地被问及 PPR 与岛屿架构的区别。

总而言之,PPR 与岛屿架构完全不同。本文将解释 PPR 和岛屿架构的区别。

PPR

首先,让我们重新梳理一下 PPR 和岛屿架构的概念。

PPR 是一种渲染模型,能够在页面进行静态渲染的同时,对部分内容进行动态渲染。具体来说,可以在构建时(或者在重新验证后)对页面进行静态生成,同时对每个请求中需要处理的动态部分进行延迟渲染。在 Next.js App Router 中,由于响应是基于流的,所以在 PPR 的情况下,静态部分会首先被快速发送,随着延迟渲染的完成,动态部分逐渐发送,并在一个 HTTP 响应中完成。

以下是 Next.js 文档 中介绍的一个电商网站商品页面的示例结构。

ppr shell

具体的行为图像可以通过官方的 DEMO 页面来清晰理解,请访问以下网址查看推荐信息和购物车信息如何从占位符换成实际内容。

https://www.partialprerendering.com/

PPR 是由 Next.js 提出并正在开发中的,目前在 Next.js v15(RC) 中可作为实验功能使用。在 Next.js 的 PPR 中,可以使用 <Suspense> 边界来区分 static/dynamic 渲染。

import { Suspense } from "react";
import { PostFeed, Weather } from "./Components";

export default function Posts() {
  // Suspense 外部是 static rendering
  return (
    <section>
      <Suspense fallback={<p>Loading feed...</p>}>
        {/* PostFeed 是 dynamic rendering */}
        <PostFeed />
      </Suspense>
      <Suspense fallback={<p>Loading weather...</p>}>
        {/* Weather 是 dynamic rendering */}
        <Weather />
      </Suspense>
    </section>
  );
}

有关更详细的内容,请参阅前述文章

岛屿架构

岛屿架构(Islands Architecture)是一种在 AstroFresh 等中采用的客户端架构。它基于一种称为部分水合作用的技术,只对需要的部分进行水合作用构建。如果查阅日文文档,Astro 的文档对理解岛屿架构可能会比较简单明了。

https://docs.astro.build/ja/concepts/islands/

岛屿架构将整个屏幕视为一个庞大的、由静态 HTML 构成的海洋,并通过一座座独立的交互式 UI 群岛(岛屿),形象地命名来描述“在广阔的海洋中存在着几个独立的岛屿”。以下是引用自 Preact 作者的博客 的岛屿架构示例。

island architecture

彩色部分表示的是岛屿。Header 包含汉堡菜单等,Sidebar 包含手风琴等交互元素,而旋转木马是交互性的典型元素。非彩色部分则是不需要 JavaScript 的静态 HTML。

PPR 与岛屿架构的区别

PPR 和岛屿架构在都是基于“静态”部分并分离出一部分功能上看似相似。前面提到的示例也显示了整体界面被视作“静态”的,并且部分功能被分离出来的样子也可能看起来相似。

然而,PPR 的重点在于服务器端渲染,而岛屿架构的重点在于客户端渲染。虽然它们都使用了“静态”这个词,但是这其中“什么是静态”的含义完全不同。

PPR 中的“静态”是指静态渲染。而岛屿架构中的“静态”是指不需要 JavaScript 的非交互式 HTML。

Client Components 与岛屿的比较

经过上述解释,您可能已经注意到,岛屿架构中岛屿相当的概念,在 React 或 Next.js 的世界中也存在。那就是 Client Components将 Client Components 嵌入到 Server Components 中的做法,与在静态 HTML 的海洋中嵌入岛屿的情形非常相似。

因此,可以说岛屿架构不是 PPR,而是与 RSC (React Server Components) 架构更为接近。

Astro 中的渲染模型

如果 Client Components 对应于岛屿,那么 Server Components 对应的是什么呢?在 Astro 中,这就是 Astro 模板。而与 PPR 相比,应当评估的是 Astro 模板和岛屿对应的渲染模型。

尽管我还没有研究 Fresh,但目前 Astro 似乎支持按页面进行 SSR 和 SSG。就在最近,开始考虑实现相当于 PPR 的 Server islands

https://github.com/withastro/roadmap/issues/945

综上所述,从 Next.js 的视角比较 Astro,可以得出以下结论:

  • PPR:支持 Server islands
  • Server / Client Components:岛屿架构

两层架构的螺旋

正如前文提到的,Dan Abramov 也提到了 Next.js 和 Astro 的比较。

https://x.com/dan_abramov2/status/1757986886390264291

  • Server and Client components
  • Astro Templates and Astro Islands
  • PHP partials and jQuery plugins

这些都是两层架构的例子。

Server Components 和 Client Components、Astro Templates 和 Astro Islands、PHP 和 jQuery plugins 等,都是两层架构的示例。外层在服务器端执行,内层在客户端(也)执行。

这个想法本身并不新鲜,它被许多人广泛使用。但这些并不完全相同。它们各自包含细微的进化,并随着时间的推移沿技术的螺旋上升。

技术选择的螺旋

https://speakerdeck.com/twada/understanding-the-spiral-of-technologies-2023-edition?slide=10

  • 技术变革的历史乍看之下似乎是钟摆
  • 但实际上是螺旋结构。我们不会回到相同的地方
  • 差异和使其成为可能的技术很重要

在过去,这个想法是通过 PHP 和 jQuery plugins 来实现的,而现在,则采用了如 Next.js 或 Astro 这样的技术。这些都归功于能够使用通用 JavaScript 实现的好处,并且它们各自经历了不同的螺旋式进化。

感想

在 PPR 中,需要设计服务器端的静态 / 动态边界。而在 RSC 架构中,则需要设计客户端的交互式 / 非交互式边界。意识到需要分别对这两层层级进行设计,你会发现岛屿架构与 PPR 完全不同,事实上,与 RSC 架构更为类似。同时,正如上一篇文章中提到的 uhyo 所说,这正是所谓的多阶段计算本质。

https://zenn.dev/uhyo/articles/react-server-components-multi-stage#用一句话理解 react-server-components

深入挖掘,我越发感觉到理解这种多阶段计算的重要性。而且,如果能把这种多阶段计算的第一阶段(服务器端)和第二阶段(客户端)分开来考虑,那么复杂的 Next.js 架构可能会变得简单明了。

尽管我试图总结,但如果有不清楚的地方,请指出。