Web渲染那些事儿

更新日期: 2019-03-04阅读: 2.8k标签: 渲染

作为开发者,经常需要面对影响整个应用架构的决策。而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 的服务器渲染指南或 Nuxtangular 有 Universal。大部分流行的解决方案采用某种 hydration 的形态,因此在选择工具之前要注意使用的方法。


静态渲染(Static Rendering)

静态渲染在构建时进行,并提供快速的 FP、FCP 和 TTI——假设客户端JS的体积得当。与服务器渲染不同,它还致力于实现始终如一的快速首字节时间(TTFB),因为页面的 HTML 不必动态生成。通常,静态渲染意味着提前为每个 URL 生成单独的 HTML 文件。通过预先生成 HTML 响应,可以将静态渲染部署到多个 CDN 以利用边缘缓存。(译注:也就是“页面静态化”)


静态渲染的解决方案选择很多,像 Gatsby 这样的工具旨在让开发人员感觉他们的应用程序是动态渲染的,而不是构建过程生成的。Jekyl 和 Metalsmith 提供更多模板驱动的方法,更加符合它们的静态特质。

静态渲染的一个缺点是必须为每个可能的 URL 生成单独的 HTML 文件。 如果无法提前预测这些 URL 的内容,或者对于具有大量不同页面的网站,这可能具有挑战性甚至是不可行的。

React 用户可能熟悉 GatsbyNext.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 或 <link rel = preload> 可以更快地提供关键脚本和数据,这将使解析器更快地完成工作。像 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依赖较低的场景。下面是一张便捷的信息图,显示了服务器到客户端的技术频谱:


原文来自:https://developers.google.com/web/updates/2019/02/rendering-on-the-web


链接: https://fly63.com/article/detial/2203

vue中数据更新变化,而页面视图未渲染的解决方案

在使用vue的时候,我们都知道它是双向数据绑定的,但是在使用不熟的情况下,经常会遇到:data中的数据变化了,但是并没有触发页面渲染。下面就整理一些出现这种情况的场景以及解决办法。

服务端渲染和客户端渲染的对比

这里结合art-template模板引擎说明。首先了解下前端页面中如何使用art-template。当不需要对SEO友好的时候,推荐使用客户端渲染;当需要对 SEO友好的时候,推荐使用服务器端渲染

解决使用vue.js未渲染前代码显示问题

在使用vue的时候,偶然发现多次刷新或者网络加载缓慢的时候,会一瞬间出现设置的模板的情况。实在很影响美观,可以使用vue现成的指令来解决这个问题:v-cloak

在微信小程序中渲染html内容的实现

大部分Web应用的富文本内容都是以HTML字符串的形式存储的,通过HTML文档去展示HTML内容自然没有问题。但是,在微信小程序(下文简称为「小程序」)中,应当如何渲染这部分内容呢?

原来 CSS 与 JS 是这样阻塞 DOM 解析和渲染的

估计大家都听过,尽量将 CSS 放头部,JS 放底部,这样可以提高页面的性能。然而,为什么呢?大家有考虑过么?很长一段时间,我都是知其然而不知其所以然,强行背下来应付考核当然可以,但实际应用中必然一塌糊涂

Vue渲染数据理解以及Vue指令

原生JS改变页面数据,必须要获取页面节点,也即是进行DOM操作,jQuery之类的框架只是简化DOM操作的写法,实质并没有改变操作页面数据的底层原理,DOM操作影响性能(导致浏览器的重绘和回流),Vue是一个mvvm框架(库),大幅度减少了DOM操作

vue从后台获取数据赋值给data,如何渲染更细视图

如果从服务端返回的数据量较少,或者只有几个字段,可以用vue的set方法,如果数据量较大,请直接看第二种情况。官网API是这样介绍的:Vue.set(target,key,value)

react 异步加载数据时的渲染问题

当数据需要异步加载时render获取不到数据可能会报一些错误,此时需要在render函数中加一个判断.行到render时,state对象的haveData为false, 所以此时页面展示 loading,当异步获取数据成功时

Vue.js中v-html渲染的dom添加scoped的样式

在vue.js中,要将一段字符串渲染成html,可以使用v-html指令。但是 官方文档 中的v-html部分也提醒了

vue 修改变量值无法渲染到页面

开发中碰到这么个问题,修改对象中的属性无法渲染页面。直接操作ccc变量就是没问题的。直接贴我的代码。解决方法一:注意,第二个参数是字符串类型,切记。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!