Web渲染那些事儿
栏目: JavaScript · 发布时间: 5年前
内容简介:作为开发者,经常需要面对影响整个应用架构的决策。而Web开发者的核心决策之一,就是应用逻辑与渲染工作的实现,应处于架构中的什么位置(译注:客户端 or 服务器?)。现在有很多不同构建网站的方法,因此这些决策变得愈加困难。我们对这一领域的理解,来自于我们过去几年在 Chrome 工作中,与大型网站的交流。从广义上讲,我们鼓励开发人员考虑通过一种称为 rehydration 的方式,进行服务器渲染或静态渲染。为了更好地理解在做出决定时所选择的架构,我们需要对每种方法有充分的理解,并且在谈到它们时使用一致的术语。
作为开发者,经常需要面对影响整个应用架构的决策。而Web开发者的核心决策之一,就是应用逻辑与渲染工作的实现,应处于架构中的什么位置(译注:客户端 or 服务器?)。现在有很多不同构建网站的方法,因此这些决策变得愈加困难。
我们对这一领域的理解,来自于我们过去几年在 Chrome 工作中,与大型网站的交流。从广义上讲,我们鼓励开发人员考虑通过一种称为 rehydration 的方式,进行服务器渲染或静态渲染。
为了更好地理解在做出决定时所选择的架构,我们需要对每种方法有充分的理解,并且在谈到它们时使用一致的术语。
术语
渲染
- SSR:服务器渲染(Server-Side Rendering) ——在服务器上将客户端或通用(universal)应用程序渲染成HTML。
- CSR:客户端渲染(Client-Side Rendering) ——在浏览器中渲染App,通常使用DOM。
- Rehydration :在客户端上“启动” JavaScript 视图,复用服务器渲染的HTML DOM树和数据。(译注:利用服务器返回HTML中的JS数据,重新渲染页面的技术,详见 知乎讨论 ,其中《三体》的部分很形象~)
- 预渲染(Prerendering) :在构建时运行客户端应用程序,以将其初始状态捕获为静态HTML。
性能
- TTFB :首字节时间(Time to First Byte)——从点击链接 到 接收第一个字节内容 之间的时间。
- FP :首次绘制(First Pain)——第一次有像素对用户可见的时间。
- FCP :首次内容绘制(First Contentful Paint)——请求内容(文章正文等)变得可见的时间。
- TTI :可交互时间(Time To Interactive)——页面变为可交互的时间(事件绑定等)。
服务器渲染(Server Rendering)
服务器渲染,指在服务器中生成整个页面的HTML,以此响应请求的技术。这样做避免了在客户端上进行数据获取的额外往返(round-trips)和模板处理,因为这些工作在浏览器获得响应之前,已由服务器处理了。
服务器渲染通常会得到快速的 首次绘制 (FP)和 首次内容绘制 (FCP)。在服务器上运行页面逻辑和渲染,可以避免向客户端发送大量 JavaScript,有助于实现快速的 可交互时间 (TTI)。这之所以行得通,因为服务器渲染的本质,只是向用户浏览器发送文本和链接。这种方法适用于广泛的设备和网络,并能触发一些有趣的浏览器优化,比如流文档解析。
使用服务器渲染,用户不再需要在客户端上等待 CPU 相关的 JavaScript 处理后,然后才能访问站点。即使 第三方JS 无法避免,使用服务器渲染来减少自己的 JS成本 ,也能提供更多的性能“ 预算 ”。但是,这种方法有一个主要缺点:在服务器上生成页面有一定耗时,可能会导致较慢的首字节时间(TTFB)。
服务器渲染是否满足应用程序,很大程度上取决于构建目标的体验类型。关于服务器渲染与客户端渲染的正确应用存在长期争论,但重要的是我们可以选择对某些页面使用服务器渲染,而对其余页面不使用。一些网站已成功采用混合渲染技术: Netflix 服务器渲染其相对静态的落地页面,同时为交互繁重的页面 预拉取 JS,为这些重客户端页面提供更快的加载能力。
许多现代框架、库和架构,使得在客户端和服务器上渲染相同的应用程序成为可能。这些技术可用于服务器渲染,但是要注意,在服务器和客户端上进行渲染的架构,都是各框架自家的解决方案,具有不同的性能特点和权衡。React 用户可以使用 renderToString() 或在其上构建的解决方案如 Next.js ,用于服务器渲染;Vue 用户可以查看 Vue 的 服务器渲染指南 或 Nuxt ;Angular 有 Universal 。大部分流行的解决方案采用某种 hydration 的形态,因此在选择 工具 之前要注意使用的方法。
静态渲染(Static Rendering)
静态渲染 在构建时进行,并提供快速的 FP、FCP 和 TTI——假设客户端JS的体积得当。与服务器渲染不同,它还致力于实现始终如一的快速首字节时间(TTFB),因为页面的 HTML 不必动态生成。通常,静态渲染意味着提前为每个 URL 生成单独的 HTML 文件。通过预先生成 HTML 响应,可以将静态渲染部署到多个 CDN 以利用边缘缓存。(译注:也就是“页面静态化”)
静态渲染的解决方案选择很多,像 Gatsby 这样的工具旨在让开发人员感觉他们的应用程序是动态渲染的,而不是构建过程生成的。 Jekyl 和 Metalsmith 提供更多模板驱动的方法,更加符合它们的静态特质。
静态渲染的一个缺点是必须为每个可能的 URL 生成单独的 HTML 文件。 如果无法提前预测这些 URL 的内容,或者对于具有大量不同页面的网站,这可能具有挑战性甚至是不可行的。
React 用户可能熟悉 Gatsby 、 Next.js 静态导出 或 Navi ——它们都可以方便使用组件。但是,了解静态渲染和预渲染之间的区别非常重要:静态渲染页面是无需执行太多客户端 JS 就可交互的,预渲染则改进了单页面应用的 FP 或 FCP,由于是单页面应用,所以必须等待客户端启动过程,以使页面真正具有交互性。(译注:简单的说静态渲染不依赖客户端JS,适用于静态页面,而预渲染则依赖JS,更多是为了富应用的初始界面加速)
如果不确定选择静态渲染还是预渲染方案,请尝试此测试:禁用JavaScript并加载创建的网页。对于静态渲染的页面,大多数功能在未启用JavaScript下仍然正常运作。而对于预渲染页面,一些基本功能(如链接)能正常展现,但页面其余部分无法正常展现。
另一个有效的测试是使用 Chrome DevTools 减慢网络速度,并观察在页面变为可交互之前已下载了多少 JavaScript。预渲染通常需要更多的 JavaScript 来实现交互,并且这些 JS 往往比静态渲染使用的 渐进增强 方法更复杂。
服务器渲染 vs 静态渲染
服务器渲染并不是银弹——它的动态特性带来 显著的计算成本 。许多服务器渲染解决方案会有耗时,导致延迟的 TTFB 或成倍的数据传输(例如,客户端 JS 所需的内联状态)。在 React 中,renderToString() 可能很慢,因为它是同步和单线程的。服务器渲染“正确”的姿势,可能涉及查找或构建 组件缓存 方案、内存消耗管理、应用 记忆化 技术以及许多其他方面。同一个应用程序通常需要多次处理/重建——一次在客户端中,一次在服务器中。因此服务器渲染可以使某些东西更快地显示出来,但并不意味着可以减少工作量。
服务器渲染为每个 URL 按需生成 HTML,但速度可能比仅提供静态渲染内容要慢。如果加以进行额外的工作,服务器渲染 + HTML缓存 ,可以大大减少服务器渲染时间。服务器渲染的优势在于,能够提取更多“实时”数据,并响应比静态渲染更完整的请求集。个性化页面就是一个不适用于静态渲染的页面类型代表。
在构建 PWA 时,服务器渲染也抛出一个有趣的问题。 整个页面使用 Service Worker 缓存,与服务器渲染部分内容片段,哪个方案更好?
客户端渲染(Client-Side Rendering,CSR)
客户端渲染(CSR)意味着使用 JavaScript 直接在浏览器中渲染页面。 所有逻辑、数据获取、模板和路由都在客户端处理,而不是服务器上。
客户端渲染很难在移动端做到很快。如果做好压缩工作, 严格控制 JavaScript 预算 ,并在尽可能少的 RTT 中提供内容,它可以接近纯服务器渲染的性能。使用 HTTP/2 Server Push 或 可以更快地提供关键脚本和数据,这将使解析器更快地完成工作。像 PRPL 这样的模式值得评估,以确保初始和后续导航的即时感。
客户端渲染的主要缺点是,随着应用程序的发展,所需的 JavaScript 数量会增加。随着添加新的 JavaScript 库、polyfill 和第三方代码,更是一发不可收拾。这些代码会竞争处理能力,并且通常必须在渲染页面内容之前完成处理。构建依赖大型 JavaScript 的 CSR 应用时,应该考虑 积极的代码分割 ,并确保延迟加载 JavaScript——“只在需要时提供所需内容”。对于很少或没有交互性的页面,服务器渲染可以作为更具扩展性的解决方案。
对于构建单页应用程序的人来说,识别大多数页面共享的UI核心部分,意味着可以应用 Application Shell 缓存 技术。与 Service Worker 相结合,可以显著提高重复访问的感知性能。
通过 Rehydration 将服务器渲染和 CSR 相结合
这种方法通常被称为通用渲染或简称为“SSR”,它试图通过两者兼顾来平滑客户端渲染和服务器渲染之间的权衡。页面请求交由服务器处理,将应用程序渲染为 HTML,然后把用于渲染的 JavaScript 和数据,嵌入到生成的文档中。只要处理得当,这就像服务器渲染一样实现了快速的 FCP,然后通过称为 (re)hydration 的技术,在客户端上再次“拾取”来渲染。这是一种新颖的解决方案,但也具有一些明显性能缺陷。
译注:如果这里不好理解,请先理解上面术语部分中 Rehydration 的知乎链接内容。
rehydration 后的 SSR 主要缺点,是它会对可交互时间(TTI)产生显著的负面影响,即使它改善了首次绘制(FP)。SSR 页面通常看起来具有欺骗性的加载完成和可交互性,但在执行客户端JS并绑定事件处理之前,页面实际上无法响应输入。这在移动设备上可能持续几秒甚至几分钟。
也许你自己也经历过这种情况——在页面看起来已经加载后的一段时间内,点击或触摸什么都没反应。这很快变得令人沮丧......“为什么没有反应? 为什么我不能滚动?“
一个 Rehydration 问题:应用的双重成本
由于JS特性,Rehydration 问题往往比延迟交互更糟糕。为了使客户端 JavaScript 能够不用重新请求服务器,就能准确地获取服务器返回的用于呈现其 HTML 的所有数据,当前的 SSR 解决方案通常将UI的数据响应序列化, 以 Script 标签形式存放在 HTML 中。结果是生成的 HTML 文档包含大量重复片段:
正如你所看到的,服务器除了返回应用程序 UI 以响应页面请求,还返回了用于组成该 UI 的源数据,以及生成相同 UI 的实现代码,即刻在客户端上运行。只有在 bundle.js 完成加载和执行后,页面才会变为可交互。
从使用 Rehydration SSR 站点收集的性能数据显示,这种用法应极力避免。归根结底,原因归结为用户体验:很容易让用户处于“不明所以”的状态。
Rehydration SSR 也不是没有希望。在短期内,仅将 SSR 用于高度可缓存的内容,可以减少 TTFB 延迟,从而达到与预渲染类似的结果。
流式服务器渲染和渐进式 Rehydration
服务器渲染在过去几年中发展迅猛。
流式服务器渲染 能以 chunk 形式发送 HTML,浏览器可以在接收时逐块渲染。这促成了快速的 First Paint 和 First Contentful Paint,因为 HTML 标签更快地到达用户侧。在 React 中,流在 renderToNodeStream() 中异步处理,相比于同步的 renderToString,服务器的压力也会更小。
渐进式 Rehydration 也值得关注,React 一直在 探索 。使用这种方法,服务器渲染后的页面各部分,随着时间推移被“启动”,而不是通常一次初始化整个应用程序的做法。这可以减少页面可交互所需的 JavaScript 量,因为可以延迟页面低优先级部分,以防止阻塞主线程。它还可以帮助避免最常见的 SSR Rehydration 陷阱:服务器渲染的DOM树被破坏后立即重建——通常是因为客户端初始同步渲染所需的数据还没准备好,比如还在等待 Promise 的解析。
部分 Rehydration
部分 Rehydration 已被证明难以实现。该方法是渐进式 Rehydration 概念的扩展,通过分析渐进式 Rehydration 的各个部分(组件/视图/树),识别出那些不具交互性的部分。对于每个基本静态的部分,相应的 JavaScript 代码会被转换为惰性引用和装饰功能,将其客户端占用空间减少到接近于零。部分 Rehydration 方案伴随着自身的问题和妥协。它为缓存带来了一些有趣的挑战,我们无法假设服务器渲染的惰性部分 HTML,在页面完整加载前是可用的。
三方同构渲染(Trisomorphic Rendering)
如果可以使用 service worker ,“trisomorphic”渲染也很有意思。该技术是指,利用流式服务器渲染初始页面,等 Service Worker 加载后,接管 HTML 的渲染工作。这可以使缓存的组件和模板保持最新,并启用 SPA 式的导航以在同一会话中渲染新视图。当可以在服务器、客户端页面和 Service Worker 之间共享相同模板和路由代码时,此方法最有效。
SEO 考虑
在选择渲染策略时,团队通常会考虑 SEO 的影响。为了让爬虫能够轻松获得“完整页面”,服务器渲染是不二的选择。虽然爬虫 可能会理解 JavaScript ,但是在渲染方式上的 局限性 需要注意。如果你的应用非常重 JavaScript,最近的 动态渲染 方案也是个值得考虑的选择。
如果有疑问, Mobile-Friendly Test 工具对于测试你选择的方法是否符合预期,非常有用。它展示了 Google 爬虫渲染页面的预览、序列化的 HTML 内容(执行 JavaScript 后),以及渲染过程中发生的错误。
总结
在决定渲染方式时,需要测量和理解真正的瓶颈在哪里。静态渲染或服务器渲染在多数情况都比较适用,尤其是可交互性对JS依赖较低的场景。下面是一张便捷的信息图,显示了服务器到客户端的技术频谱:
以上所述就是小编给大家介绍的《Web渲染那些事儿》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。