Rendering on the Web

作为开发人员,咱们常常面临会影响应用程序整个架构的决策。 Web 开发人员必须做出的外围决策之一是在他们的应用程序中实现逻辑和出现的地位。 这可能很艰难,因为有许多不同的办法来构建网站。

咱们对这一畛域的了解源于咱们过来几年在 Chrome 中与大型网站的交换工作。 从狭义上讲,咱们激励开发人员思考服务器渲染或动态渲染,而不是齐全从新 hydration 的办法。

为了更好地了解咱们在做出这个决定时抉择的架构,咱们须要对每种办法和在议论它们时应用的统一术语有粗浅的了解。 这些办法之间的差别有助于从性能的角度阐明在 Web 上渲染的衡量。

渲染

  • SSR:服务器端渲染 - 在服务器上将客户端或通用应用程序渲染为 HTML。
  • CSR:客户端渲染 - 在浏览器中渲染应用程序,通常应用 DOM。
  • Rehydration:在客户端“启动”JavaScript 视图,以便它们重用服务器渲染的 HTML 的 DOM 树和数据。
  • Prerendering:在构建时运行客户端应用程序以将其初始状态捕捉为动态 HTML。
  • TTFB:Time to First Byte - 被视为单击链接和第一个内容进入之间的工夫。
  • FP:First Paint - 任何像素第一次对用户可见。
  • FCP:First Contentful Paint - 申请的内容(文章正文等)变得可见的工夫。
  • TTI: Time To Interactive - 页面变为可交互的工夫(事件连贯等)。

Server Rendering

服务器渲染为服务器上的页面生成残缺的 HTML 以响应导航。 这防止了在客户端上进行数据获取和模板化的额定往返,因为它是在浏览器取得响应之前解决的。

服务器渲染通常会产生疾速的首次绘制 (FP) 和首次内容绘制 (FCP)。 在服务器上运行页面逻辑和渲染能够防止向客户端发送大量 JavaScript,这有助于实现疾速交互工夫 (TTI)。 这是有情理的,因为通过服务器渲染,您实际上只是向用户的浏览器发送文本和链接。 这种办法能够很好地实用于各种设施和网络条件,并开启了乏味的浏览器优化,例如流式文档解析。

通过服务器渲染,用户不太可能在应用您的网站之前期待 CPU 绑定的 JavaScript 解决。即便无奈防止第三方 JS,应用服务器渲染来升高您本人的第一方 JS 老本也能够为您提供更多的“估算”。然而,这种办法有一个次要毛病:在服务器上生成页面须要工夫,这通常会导致首字节工夫 (TTFB) 变慢。

服务器渲染是否足以满足您的应用程序在很大水平上取决于您正在构建的体验类型。对于服务器渲染与客户端渲染的正确应用程序存在长期争执,但重要的是要记住,您能够抉择对某些页面应用服务器渲染而不是其余页面。一些网站曾经胜利地采纳了混合渲染技术。 Netflix 服务器出现其绝对动态的登陆页面,同时为交互密集型页面预取 JS,为这些较重的客户端出现页面提供更好的疾速加载机会。

许多古代框架、库和架构使得在客户端和服务器上出现雷同的应用程序成为可能。这些技术可用于服务器渲染,但重要的是要留神渲染产生在服务器和客户端的架构是它们本人的解决方案类,具备十分不同的性能特色和衡量。 React 用户能够应用 renderToString() 或基于它构建的解决方案,例如 Next.js 进行服务器渲染。 Vue 用户能够查看 Vue 的服务器渲染指南或 Nuxt。 Angular 有 Universal framework.大多数风行的解决方案都采纳某种模式的 hydration 作用,因而在抉择工具之前请留神应用的办法。

Static Rendering

动态渲染产生在构建时,并提供疾速的首次绘制、首次内容绘制和交互工夫 - 假如客户端 JS 的数量无限。 与服务器渲染不同,它还设法实现始终如一的疾速首字节工夫,因为页面的 HTML 不用即时生成。 通常,动态出现意味着提前为每个 URL 生成独自的 HTML 文件。 通过提前生成 HTML 响应,能够将动态出现器部署到多个 CDN 以利用边缘缓存。

动态渲染的解决方案有各种形态和大小。 Gatsby 之类的工具旨在让开发人员感觉他们的应用程序是动静出现的,而不是作为构建步骤生成的。其余像 Jekyll 和 Metalsmith 这样的公司拥抱它们的动态个性,提供了一种更加模板驱动的办法。

动态出现的毛病之一是必须为每个可能的 URL 生成独自的 HTML 文件。如果您无奈提前预测这些 URL 的内容,或者对于具备大量独特页面的网站,这可能具备挑战性甚至不可行。

React 用户可能相熟 Gatsby、Next.js 动态导出或 Navi - 所有这些都能够不便地应用组件进行创作。然而,理解动态渲染和预渲染之间的区别很重要:动态渲染页面是交互式的,无需执行大量客户端 JS,而预渲染改良了必须启动的单页应用程序的首次绘制或首次内容绘制客户端以使页面真正具备交互性。

如果您不确定给定的解决方案是动态渲染还是预渲染,请尝试以下测试:禁用 JavaScript 并加载创立的网页。对于动态出现的页面,大多数性能在没有启用 JavaScript 的状况下依然存在。对于预渲染页面,可能依然有一些基本功能,如链接,但大部分页面将是惰性的。

另一个有用的测试是应用 Chrome DevTools 升高网络速度,并察看在页面变为可交互之前已下载了多少 JavaScript。预渲染通常须要更多的 JavaScript 能力取得交互性,而且 JavaScript 往往比动态渲染应用的渐进加强办法更简单。

Server Rendering vs Static Rendering

服务器渲染不是灵丹妙药——它的动静个性会带来大量的计算开销老本。许多服务器渲染解决方案不会提前刷新,可能会提早 TTFB 或将发送的数据加倍(例如,客户端上的 JS 应用的内联状态)。在 React 中,renderToString() 可能很慢,因为它是同步的和单线程的。使服务器渲染“正确”可能波及为组件缓存找到或构建解决方案、治理内存耗费、利用记忆技术以及许多其余问题。您通常会屡次解决/重建同一个应用程序 - 一次在客户端上,一次在服务器中。仅仅因为服务器渲染能够使某些货色更快地显示进去并不忽然意味着您要做的工作更少。

SSR 为每个 URL 按需生成 HTML,但可能比仅提供动态出现的内容慢。如果您能够进行额定的工作,服务器渲染 + HTML 缓存能够大大减少服务器渲染工夫。与动态渲染相比,服务器渲染的劣势在于可能提取更多“实时”数据并响应更残缺的申请集。须要个性化的页面是不能很好地与动态出现一起工作的申请类型的具体示例。

在构建 PWA 时,服务器渲染也能够提供乏味的决策。应用全页面 Service Worker 缓存还是仅服务器渲染单个内容更好?

Client-Side Rendering (CSR)

客户端渲染 (CSR) 是指应用 JavaScript 间接在浏览器中渲染页面。 所有逻辑、数据获取、模板和路由都在客户端而不是服务器上解决。

对于挪动设施,客户端渲染可能难以获得并放弃疾速。 如果做起码的工作,放弃缓和的 JavaScript 估算并以尽可能少的 RTT 交付价值,它能够靠近纯服务器渲染的性能。 应用 HTTP/2 服务器推送或 \<link rel=preload> 能够更快地交付要害脚本和数据,这能够让解析器更快地为您工作。 像 PRPL 这样的模式值得评估,以确保初始和后续导航感觉即时。

客户端渲染的次要毛病是所需的 JavaScript 量会随着应用程序的增长而增长。 增加新的 JavaScript 库、polyfill 和第三方代码后,这变得尤其艰难,它们会抢夺解决能力,并且通常必须在出现页面内容之前进行解决。 应用依赖于大型 JavaScript 包的 CSR 构建的体验应该思考踊跃的代码拆分,并确保提早加载 JavaScript——“只在须要时提供您须要的服务”。 对于交互性很少或没有交互性的体验,服务器渲染能够代表这些问题的更具可扩展性的解决方案。

对于构建单页应用程序的人来说,辨认大多数页面共享的用户界面的外围局部意味着您能够利用应用程序外壳缓存技术。 与服务工作者相结合,这能够显着进步反复拜访时的感知性能。

Combining server rendering and CSR via rehydration

通常被称为 Universal 渲染或简称为“SSR”,这种办法试图通过同时进行客户端渲染和服务器渲染之间的衡量来均衡。诸如整页加载或从新加载之类的导航申请由将应用程序出现为 HTML 的服务器解决,而后用于出现的 JavaScript 和数据被嵌入到生成的文档中。如果认真施行,这将实现疾速的第一次内容绘制,就像服务器渲染一样,而后通过应用称为(从新)Hydration 的技术在客户端再次渲染来“拾取 Pick up”。这是一个新鲜的解决方案,但它可能有一些相当大的性能缺点。

带有再 hydration 的 SSR 的次要毛病是它会对交互工夫产生显着的负面影响,即便它改良了 First Paint。 SSR 的页面通常看起来像是在加载和交互,但在执行客户端 JS 并附加事件处理程序之前,它们实际上无奈响应输出。在挪动设施上这可能须要几秒钟甚至几分钟的工夫。

兴许您本人也经验过这种状况 - 在看起来页面已加载后的一段时间内,单击或点击什么也没做。这很快变得令人丧气......“为什么什么都没有产生?为什么我不能滚动?”

A Rehydration Problem: One App for the Price of Two

Rehydration 问题通常比 JS 导致的提早交互更蹩脚。 为了让客户端 JavaScript 可能精确地“接管”服务器中断的地位,而不用从新申请服务器用于出现其 HTML 的所有数据,以后的 SSR 解决方案通常将来自 UI 的响应序列化 数据依赖项作为脚本标签写入文档。 生成的 HTML 文档蕴含高度反复:

如您所见,服务器响应导航申请返回应用程序 UI 的形容,但它也返回用于组成该 UI 的源数据,以及 UI 实现的残缺正本,而后在客户端启动 . 只有在 bundle.js 实现加载和执行后,这个 UI 才会变成交互式。

从应用 SSR 再水化的实在网站收集的性能指标表明,应强烈建议不要应用它。 归根结底,起因归结为用户体验:最终很容易让用户陷入“恐怖谷”。

Streaming server rendering and Progressive Rehydration

在过来的几年中,服务器渲染有了许多倒退。

流式服务器出现容许您以块的模式发送 HTML,浏览器能够在接管到时逐渐出现这些 HTML。这能够提供疾速的首次绘制和首次内容绘制,因为标记更快地达到用户手中。在 React 中,流在 renderToNodeStream() 中是异步的——与同步 renderToString 相比——意味着 backpressure 失去了很好的解决。

渐进式 rehydration 也值得关注,这是 React 始终在摸索的货色。应用这种办法,服务器渲染的应用程序的各个局部会随着工夫的推移而“启动”,而不是以后一次初始化整个应用程序的罕用办法。这有助于缩小使页面具备交互性所需的 JavaScript 量,因为能够推延页面低优先级局部的客户端降级以避免阻塞主线程。它还能够帮忙防止最常见的 SSR Rehydration 陷阱之一,其中服务器渲染的 DOM 树被毁坏而后立刻重建——通常是因为初始同步客户端渲染须要尚未筹备好的数据,可能正在期待 Promise解析度。

SEO Considerations

在抉择在 Web 上出现的策略时,团队通常会思考 SEO 的影响。 通常抉择服务器渲染来提供爬虫能够轻松解释的“残缺外观”体验。 爬虫可能了解 JavaScript,但在它们的出现形式中通常有一些值得关注的局限性。 客户端渲染能够工作,但通常须要额定的测试和工作。 如果您的架构很大水平上由客户端 JavaScript 驱动,那么最近动静渲染也成为一个值得思考的选项。

如有疑难,Mobile Friendly 测试工具对于测试您抉择的办法是否合乎您的冀望十分有用。 它显示了任何页面在 Google 抓取工具中的显示方式、找到的序列化 HTML 内容(在执行 JavaScript 之后)以及出现期间遇到的任何谬误的视觉预览。

总结

在决定渲染办法时,测量并理解您的瓶颈是什么。 思考动态渲染或服务器渲染是否能够让您实现 90% 的工作。 应用起码的 JS 来次要公布 HTML 以取得交互体验是齐全能够的。 这是一个不便的信息图,显示了服务器 - 客户端范畴:

更多Jerry的原创文章,尽在:"汪子熙":