为什么选择服务器组件 - Web 简史
根据2023年Stack Overflow的调查,Next.js 现在是第三大最受欢迎的 Web 框架,也是开发者中第六大最受赞赏的框架。尽管他们的旧Pages Router系统非常优秀,但他们在框架13版中引入了新的App Router路由系统。随着这一变化,他们还迁移到使用Server Components,这可以被视为下一级服务器渲染。
为了理解 Next.js 和 Server Components 为什么比传统的客户端 React 应用更加强大,本文将带你从互联网的起源开始,介绍不同类型的服务器端渲染及其各自的优点,一直到最新的 web 开发状态,其中就包括 Next.js Server Components。
从服务器渲染到 SPA,然后返回服务器
为了更好地理解Server Components,让我们进行一个简短的历史回顾。
在过去的传统 Web 编程时代,网站由服务器渲染 HTML 代码,将其发送到客户端浏览器,浏览器显示已经渲染好的 HTML 代码给用户。与此同时,使用 CSS 和一些小的 JavaScript 文件来处理用户交互和导航到网站的其他页面(还有展示一个很酷的访客计数器,这似乎是必需的)。
随着新技术的出现,我们拥有了智能手机和性能更好的计算机。网络得到了发展,并且单页面应用程序(SPA)成为了新的标准,这基本上意味着只有一个初始网页被发送到客户端。在用户导航或与页面交互时,使用 REST 请求从服务器检索数据,并修改页面而无需实际导航到网站的另一个页面。
在这一演变过程中,最重要的推动者之一是 jQuery 库,然后这一原则随着现代框架如 React、Angular 和 Vue 的出现而继续发展。这些框架首先在客户端上渲染所有内容,并支持选择性地使用服务器端渲染(SSR)。随着网络的转变为 SPA,我们开始将拥有丰富功能的网站称为 Web 应用程序。
越来越多的 Web 应用程序框架涌现出来,各种预渲染 Web 应用程序的方法也应运而生。使用 SSR 在请求时预渲染网页已经不够了,框架还增加了在构建时预渲染页面的支持,称为静态站点生成(SSG)。
在这篇文章中,我们很快将更详细地了解 SSR 和 SSG,并在文章末尾讨论服务器渲染的优缺点。但现在,让我们暂且不谈为什么,而是专注于了解什么是以及如何使用客户端组件和服务器组件。
我们目前所处的阶段已经不再是关于客户端渲染或服务器端渲染的问题,实际上,我们已经到达了一个可以选择以不同方式渲染网站的阶段,而这正是客户端组件和服务器组件的用武之地。
客户端组件与服务器组件
如果您已经使用过像 React 或 Angular 这样的 SPA 框架,您应该对客户端组件(Client Components)非常熟悉。客户端组件本质上就是 React 组件的本质,它是一小段 JavaScript 代码,当它在浏览器中被加载时,会在网页上呈现一个组件。
另一方面,服务器端组件(Server Components)是在服务器上渲染成 HTML 的组件。当我说这个时候,我并不是指通过 SSR 或 SSG 来预渲染整个页面的 HTML,而是指实际上渲染一个单独的组件,比如一个单独的 React 组件。这就是 Next.js 在新的 App Router 系统中提供的粒度。
为了更好地理解,让我们先看一下旧的预渲染解决方案,然后再回过头来讨论服务器端组件。
服务器端渲染 (SSR) 与静态站点生成 (SSG)
SSR 和 SSG 的基本原理是相同的,在服务器上预渲染 HTML 代码,然后获取 JavaScript 以使页面在称为水合作用的过程中进行交互。根据框架的不同,导航可以导致从服务器获取全新的页面,也可以像 SPA 一样由水合 JavaScript 代码处理。
预渲染将获取 HTML,然后水合 JavaScript 以使页面具有交互性
当使用 SSR 作为预渲染解决方案时,请求页面,完整的网页会在服务器上渲染为HTML。然后服务器还将获取 JavaScript 并启动水合过程。
当浏览器请求时,服务器端渲染会渲染页面的 HTML
相反,SSG 允许您在构建时预渲染应用程序,这样就可以快速向任何用户提供相同的 HTML 页面,而无需在运行时渲染页面。
静态站点生成在构建时呈现 HTML 并根据所有请求提供服务
虽然 SSG 允许提供即时网页,但它不允许为不同的用户提供不同的 HTML。所有用户都将获得相同的内容。基于用户数据的动态渲染是不可能的。
SSG 也不允许您按时间更改 HTML,因为所有 HTML 代码都是在构建期间生成的。为了解决这个问题,Next.js 支持所谓的增量静态再生 (ISR)。它的作用是允许您手动、按需或定期重新生成静态生成的页面。
尽管 ISR 存在于旧的 Pages Router 中,但它在新的 App Router 中不可用。它已将接力棒传递给服务器组件。
重新审视客户端组件与服务器组件
现在我们了解了在服务器上渲染页面的旧选项,可以回过头来看看客户端组件和服务器端组件的实际含义。看这张图片。它展示了一个由交替的客户端组件和服务器端组件构成的网页。
页面可以由多层客户端和服务器组件组成
正如本文前面提到的,服务器组件允许您纯粹在服务器上渲染各个组件,而同一页面上的其他组件仍保留为在客户端上渲染的客户端组件(使用 Next.js,它们在服务器上预渲染一次,并且给客户补充水分)。上面的图片显示了这种情况的一个例子。绿色组件在服务器上被渲染成 HTML,而蓝色组件在客户端上使用 JavaScript 渲染。
这意味着 Next.js 现在允许为每个组件决定是该在服务器上预渲染还是需要在客户端上渲染。正如我们稍后将看到的,在静态与动态渲染部分中,Next.js 还将自动确定这些服务器组件是否需要在运行时渲染,或者是否可以在构建时提前渲染。
啊啊啊,谁在那儿?
我应该使用什么渲染解决方案
正如我们所看到的,有很多选项可用于渲染现代 Web 应用程序。如果您使用 Next.js 13,则可以在讨论的所有选项之间进行选择。如果您开始一个新项目,我建议使用新的 App Router 系统。App Router 已做好生产准备,但其他一些新的相关功能仍处于 alpha 或 beta 阶段。
为了让您的生活更轻松,我们将研究为 Pages Router 和新的 App Router 选择哪些渲染解决方案。
Next.js 页面路由器
使用 Pages Router,您有四个渲染选项可供选择。
- 客户端渲染 (CSR)
- 服务器端渲染 (SSR)
- 静态站点生成 (SSG)
- 增量静态再生 (ISR)
请记住,所有这些选项都是针对每个页面的。通常情况下,一个应用程序会同时使用这些选项。例如,信息页面(例如关于我们页面)通常可以使用 SSG 生成,而其他具有更多动态数据的页面可能会使用 SSR。
正如我们在文章后面将看到的那样,现代 web 开发倾向于更喜欢服务器端渲染而不是客户端渲染。因此,CSR 只有在确实需要时才被推荐使用。以下是一些必要或推荐在客户端上渲染应用程序的情况:
- 当您需要使用浏览器特定的 Web API 时
- 当您需要通过地理定位 Web API 或 IP 地理定位 API 获取用户的真实位置时
- 当您的数据依赖于浏览器中存储的数据(例如 LocalStorage)时
- 当应用程序在对等网络中进行通信时
- 当数据变化非常频繁时,我们可能希望将负载保持在客户端上,以免服务器过载
在大多数其他情况下,建议在服务器上进行某种预渲染。目的是尽可能长时间地缓存生成的内容。如果站点是完全静态的,如果可能的话,通常首选 SSG。
如果您有太多页面需要在构建时渲染,或者静态页面需要偶尔更新,ISR 可能是一个很好的救援方法。使用 ISR,可以在需要时在运行时生成构建,然后手动或通过设置缓存验证时间重新生成。
如果页面不是完全静态的,并且数据不仅随时间变化,而且还依赖于经过身份验证的用户信息或请求标头,则您可能需要使用 SSR 或 CSR。
Next.js 应用程序路由器
使用 App Router 时,我们没有太多的选择,主要的选项是客户端组件或服务器端组件。
当我们使用 App Router 时,静态和动态渲染的概念仍然相关,但不是那么突出。有关它的细节有点复杂,但将在[静态与动态渲染]部分中讨论。
易于理解的部分是何时使用服务器端组件。就像在 Pages Router 中一样,使用 App Router 时,优先选择服务器端渲染。这意味着只要没有使用客户端组件的原因,我们就应该使用服务器端组件。这也是为什么 Next.js 默认使用服务器端组件。
可能听起来有些奇怪,但使用客户端组件的情况与在 Pages Router 中使用 CSR 有些微妙的区别。原因是因为 App Router 的整个底层结构和设计是不同的,因此我们不能使用完全相同的规则。
使用 App Router 时,您可以遵循一个简单的规则。如果需要以任何方式使用浏览器,或者需要存储某些可能会因用户交互而更改的状态,那么您应该选择客户端组件,否则可以使用服务器端组件。
更明确地说,在以下情况下将需要客户端组件:
- 当您需要使用浏览器特定的 Web API 时
- 当您需要通过地理定位 Web API 或 IP 地理定位 API 获取用户的真实位置时
- 当您的数据依赖于浏览器中存储的数据(例如 LocalStorage)时
- 组件使用生命周期钩子或内部状态,例如 useState、useReducer、useEffect 或 useContext
- 该组件使用自定义钩子,该钩子使用生命周期钩子或内部状态
- 组件需要是交互式的,即当它在 DOM 元素上使用 onClick 或 onChange 侦听器时
- 如果由于某种奇怪的原因你仍在使用 React 类组件
这可能看起来很多,但请记住,大部分 Web 应用程序通常是静态的,并且永远不会更改或交互。例如,整个文章本身都是静态的,只是文本和链接。
服务器端组件不仅仅是在构建时生成的静态页面。后面我们会看到,您仍然可以在其中从 API 获取数据,甚至包括动态的非缓存数据。您可以读取 cookies 和 headers,并且它们允许您在服务器上读取敏感数据,并避免将大量服务器响应和 JavaScript 依赖项发送到客户端。
别担心,服务器已包含宜家风格的组装说明
静态与动态渲染
如前所述,服务器组件可以在构建时(静态)或在请求传入时在运行时(动态)呈现。这是您应该注意的事情,但不幸的是,其中会涉及许多条件。
Next.js 试图自动处理这个问题。它默认使用静态渲染,并在必要时自动启用动态渲染,或者当您手动配置时也可以使用动态渲染。动态渲染将在以下情况下激活:
- 使用动态函数,它们是读取 cookie、标头或搜索参数的内置函数
- 禁用内置 fetch 函数的缓存;
- 您可以使用Route Segment Config手动更改行为
我相信您可能需要阅读这些链接来理解更多细节,毕竟这篇文章已经足够长了,不需要再加入这些内容。然而,有一件事您应该注意,那就是使用 Next.js 内置的数据获取、cookies、headers 和搜索参数时,行为与使用自定义解决方案或外部库时是不同的。
如果您不使用 Next.js 的内置功能,Next.js 将无法知道何时必须将您的组件设置为动态。在这种情况下,您可能需要手动配置缓存行为。
服务器渲染的优点和缺点
如前面在文章中多次承诺的,我们现在将看一下服务器端渲染的优缺点。服务器端渲染有很多优点和一些缺点。优点略有不同,取决于使用的服务器渲染类型,静态站点生成(SSG)的优点可能与服务器端渲染(SSR)不同。这里所提到的优缺点是对于服务器端渲染内容的一般性描述。
服务器端渲染数据的优点
- 服务器到数据库的网络延迟通常比客户端低,特别是当客户端的数据库连接通过服务器时
- 单个或几个服务器可以比数千或数百万客户端更好地利用捕获
- 解析 JavaScript 是一项缓慢的操作,也是网站速度变慢的最大原因之一。在服务器上呈现数据可减少发送到客户端的 JavaScript 包大小
- 使用服务器端渲染,初始页面加载更快,这对用户体验(UX)和搜索引擎优化(SEO)都有好处。
- 总体上,服务器端渲染对于 SEO 更有利,因为它涉及的 JavaScript 较少,而历史上,JavaScript 在与网络爬虫的关系上一直存在问题。
- 您可以在服务器端呈现的内容中使用 API 密钥等机密。此类敏感信息绝不能用于客户
- 通过服务器端渲染,您可以将 API 和数据库保持私密并在公共互联网上不可访问,因为没有客户端依赖它。而如果客户端依赖它,除非进行身份验证,否则几乎不可能实现这一点。
- 当在服务器上获取数据时,无需显示加载旋转器或骨架
- 当在服务器上获取数据时,累积布局偏移将不会成为问题
- 当应用程序必须支持不同的浏览器和设备时,服务器上呈现的内容更容易预测,因为服务器会解释 JavaScript。在客户端,甚至可以禁用 JavaScript
服务器端渲染数据的缺点
- 服务器上渲染内容会导致更高的服务器负载,进而导致更多的服务器费用
- 服务器对第三方库的支持普遍较少
- 在服务器上,您无法访问浏览器功能、用户的位置和设备等。
- 如果数据没有被重用,每次导航都导致完全新的页面,那么导航的加载时间可能会很慢。这个缺点在传统的服务器端渲染解决方案中更为突出,因为渲染的内容很少被重用。
我想知道沃尔多去哪儿了?
服务器组件具有额外的优势
对于 React 服务器端组件(Server Components),所有上述列出的服务器端渲染的优缺点都适用,但是服务器端组件还具有一些额外的很酷的优点。
一个有趣的细节是,当客户端收到应用程序时,所有服务器端组件已经预渲染。这意味着即使条件渲染的服务器端组件依赖于网络数据,它们也可以立即渲染出来。不需要等待网络响应并处理加载状态,即使在组件根据用户操作进行条件渲染的情况下也是如此。
阅读这一点,您可能会担心捆绑包大小。我可以告诉您,您的担忧是没有必要的。HTML 代码对加载时间没有太大影响。HTML 可以被缓存,解析和运行比 JavaScript 更快,并且仅占图像大小的一小部分。
作为锦上添花,服务器端组件在 JavaScript 依赖项方面添加了零捆绑包大小!即使您使用了一个未进行 Tree Shaking 处理的 250 kB 库,生成的 HTML 代码仍然包含 0 kB 的 JavaScript。
服务器组件并不完美
如果服务器组件如此神奇,它们真的完美吗?答案当然是否定的。存在一些潜在的不便的缺点。
服务器组件需要新知识。从旧的 Next.js Pages Router 到新的 App Router 的迁移带来了一种大多数开发人员不习惯的全新思维方式。
不仅开发人员的知识变得更加过时,代码也变得如此。尽管较新版本的 Next.js 与旧的 Pages Router 完全兼容,但使用 Pages Router 的代码将成为遗留代码,就像 React 的旧类组件一样。
代码质量还存在一些其他缺点。当渲染进一步分为客户端渲染和服务器渲染时,共享代码变得更加困难。标记为use client 和import server-only 的代码使得编写 DRY 代码变得更加麻烦。
我还必须承认,尽管固执己见,但 Next.js 中服务器组件的 prop 钻探并不是可读性的最佳解决方案。在 Next.js 中,服务器组件无法导入到客户端组件中,这意味着客户端组件中渲染的服务器组件必须作为子 props 传递并照此渲染。它有效,而且不是一种新模式,但适应起来可能有点麻烦。
哦,你是 Waldo,在服务器上!
结论
Web始于从服务器检索HTML,并很少使用JavaScript。随着时间的推移,在不同的渲染解决方案(如单页应用程序(SPA)、服务器端渲染(SSR)和静态站点生成(SSG))之间反复变换后,React现在发展成为使用服务器端组件的方式。
Next.js提供了传统的Pages Router,它与非常有用的增量静态再生(ISR)策略一起使用旧的渲染选项。随着引入服务器端组件,它们现在提供了一个新的App Router,允许您在组件级别使用服务器端组件来微调您的Web应用程序的渲染。
在服务器端组件的所有好处中,我们有较低的捆绑包大小、快速的页面加载时间、出色的SEO兼容性和浏览器支持、UX改进以及使用存储在服务器上的秘密的可能性。除此之外,我们可以对条件渲染的组件进行即时渲染,无需显示任何加载微调器或骨架屏。