背景
本文是一篇SPA的介绍,讲解了SPA当前的现状需要注意的几点内容,也有基于此文的相关实践《VIP客户的可观测性实践》。
异军突起的SPA
Single Page application,译为单页面应用,也就是SPA应用,相比多页面应用有很多优势。对于使用react或者vue的js开发者来说,spa给前端应用逻辑带来了极大的灵活性,降低了对后端和运维的依赖。
对于用户来说,SPA应用能提供高度可交互的UI和更快的页面加载,带来更流畅的用户体验。当然,随着页面复杂度的攀升,也有一些不足(tradeoffs)。比如,用户可能使用老款、性能差的设备,而开发者去测试这些设备,可能就意味着要增加额外的复杂度,这对开发者无疑是一种负担。开发者需要观测终端用户体验,才能保证网站在所有设备上的流畅度。
拖后腿的观测方法和工具
然而,前端观测方法和工具的发展程度,远远跟不上javascript和SPA应用的发展。传统的前端性能监控只是依赖监测页面的加载的耗时情况。对于SPA应用来说,虽然只有一个页面,传统性能监控仅仅针对最开始页面加载的几秒钟。而且随着用户在SPA 应用中跳转,浏览器会重新根据新数据状态渲染页面组件,而不是重新加载document。
想要精确监控应用的性能,就需要追踪能影响用户体验的动态页面元素,包括动画、api调用和渲染生命周期。
就前端而言,建议从用户的视角入手,通过以时间为维度监听浏览器事件和追踪用户行为。Google和W3C性能工作组把观测用户交互作为网站性能的基础,为此已重新定义新的API。使用这些api能分解交互元素,细颗粒度的分解交互元素的时间耗时和构建应用的可视化。
本文结构
本文将专门讲解观测单页应用的三个方面来改善用户体验:
- 路由变化,记录页面跳转耗时
- 用户交互,保证流畅用户体验
- 收集错误,捕获和调试错误详情
路由变化
越来越复杂的SPA 路由
web应用中流畅的切换速度对于用户体验至关重要。假如电商单页应用包含促销的落地页,落地页中有很大的宣传产品的图片,用户可以通过页面或者链接访问,或者点击进入主页面。此时,为了保障页面平滑跳转,就需要决定图片需要在两秒甚至更短的时间内加载。一旦超过耗时,用户就会失望离开。
测量网站切换的常见方法是追踪最大内容渲染(LCP),根据最大的元素(图片、video、svg)能够可见的时间来推测页面何时加载结束从而页面可见。但是因为SPA修改了页面路由后,重新渲染页面组件,老页面的最大元素不会再新的页面重新加载,所以测量加载时间就需要找到方式检测浏览器得变化,而不是单独依靠标准的加载事件。下图展示传统页面和spa浏览器加载事件。
除了页面加载事件,建议监听路由变化并用于计算性能指标。
监听路由变化
为了追踪路由变化,需要在代码中插桩监听浏览器不同事件(而不只是监听完整的页面加载)。Timing API方便用户自定义加载的指标,满足用户精确计算页面和资源加载的时间。
History的API
首先,使用浏览器 History的API,监听“popstate”事件,该事件会在浏览器路由变化时被触发(此时需要考虑到该事件可能表示一个完整的新页面的情况);其次,使用用户Timing api设置一个·window.performance.mark()·用来标识路由变化已经开始。
window.onpopstate = function(e) { window.performance.mark('route_change_start'); } ...after requests finish and components render window.performance.measure('route_change','route_change_start')
一旦选择的新页面组件作为就绪指标被渲染后,就可以把指标传递
给’window.performance.measure‘这个函数,随后函数会创建结束标识并计算两者之间的差值。这样很容易记录应用路由切换的加载时间,继而发送给日志服务,比如观测云。
在计算每个路由变化事件的时间时,追踪每个资源的加载时间对于定位缓慢发生位置也非常有帮助。下图是一个路由变化的瀑布图,图中展示了加载字体和json文件的耗时。能看到整个页面完全加载耗时2.26s,很容易能精确看出哪些文件导致网站缓慢,看到哪些文件加载时的错误。
自动化:节省时间
手动追踪路由变化需要为每次页面切换增加标识,这样非常麻烦。另一种计算页面切换耗时的方法是先创建一个循环,一旦检测到路由变化就触发循环。比如,可以在初次性能标识创建后,在应用中插桩,每100ms检查一次,看是否有网络请求或者dom变化。如果期间发生变化或者dom 加载,循环就等待100ms再次触发。否则,就把之前的100ms作为加载时间。观测云是用类似的方法来追踪SPA页面变化。
用户交互
追踪用户使用应用的关键交互行为的耗时能暴露“缓慢”。为了改善用户体验,应该尽可能的多收集细颗粒度的信息。
继续使用电商网站的例子,当用户把商品添加到购物车时,用户的动作触发一个模态弹窗,弹窗内包含一些推荐的产品。这个关键交互需要重点监控,因为这个动作触发了浏览器很多行为,包含一系列的api调用,同时更新数据状态,还有组件的重渲染。以上任意的任务latency都能指数级增加耗时,如果用户使用移动设备或旧设备。如果弹窗加载耗时太久,或者反馈耗时太久,用户购物意愿受阻,网站可能就要丢失一个客户了。
监听用户事件event listeners
在路由切换时,可以给关键的UI元素添加监听事件event listeners,比如按钮点击、滚屏和按钮,因为它们都能触发状态变更。添加监听事件有利于快速追踪重要的用户交互的加载时间和定位耗时最久的情况。需要强调的是,经证实,超过100ms这个临界值,用户交互便不自然不流畅。
全链路监控的重要性
SPA页面中关键用户交互行为,包括modal flow,通常依赖多个隐藏在屏幕背后的任务,比如api调用、数据状态变更以及组件渲染。这就意味着监控这些后端处理的性能对于判断前端latency的原因非常重要。比如产品modal的缓慢可能来源于api接口的超时,而这一点仅仅监控页面加载是不够的。能够看到各个技术栈每层的情况有助于定位是代码或者数据库导致了前端缓慢的问题。
比如,下图能看到前端触发请求/products.json接口的请求火焰图。我们能够分解对此次请求出发的网络请求和后端任务进行分解。每个span都包含耗时和执行的详细上下文,包括过程中可能出现的错误。清晰的时间线和api动作的层次体系,就能立刻判别出 NoMethodError这个错误源于controller中。如果不能可视化的看到技术栈中请求的过程,就不会很容易的定位用户体验的根因。
前端监控long task有助于判断影响用户体验的卡顿和脚本优化。long task是指影响UI主线程超过50ms的js代码。可以在浏览器控制台看到。
错误追踪
SPA应用在用户设备上要运行更多更复杂的代码,如果不在代码插桩是无法发现浏览器错误的。如果不埋点,当发现需求支持的订单或者业务单量下降时,就只能亡羊补牢。捕获和记录应用错误有助于防患于未然。比如,如果电商网站下单按钮无法连接后端api,就会产生浏览器错误。如果用户点击下单但无反应,用户可能不会很开心,也可能去其他网站。如果不追踪用户浏览器错误,那就只能发现从购物车到下单下降的结果。
也可以通过全局事件,比如window.onError,来捕获和记录错误事件。在记录错误时,应该尽可能多的收集浏览器信息、os、应用版本等。这些数据属于应用采集常见的数据,这些信息为错误的影响和后续的调试提供更多的上下文信息。
为了高效的追踪错误,需要降噪。比如第三方包、浏览器扩展程序,以及依赖等可能报错,虽然这些错误实际与网站性能无关,但是过滤这些数据寻找有效信息太难了。使用第三方监控方案聚合相似的错误日志有助于分析,帮助定位关键问题。下面我们列出错误追踪的截图,图中能清楚的了解错误情况,同时sourcemap功能能定位到代码行数。
追踪错误
追踪应用在真实设备上的错误能让测试和开发二次调试的成本降低,因为能及时发现和收集错误的上下文环境中的详细信息提高调试效率,从而避免更大的影响到用户交互,比如添加商品到购物车或者下单支付。
有的第三方包,比如观测云能让用户实时追踪浏览器的错误日志,尤其是发生错误时的上下文场景。
观测SPA应用
随着前端框架和SPA应用复杂度演进,用户设备上出现大量渲染行为,性能监测的难度也越来越高。所以才建议把前端监控视为监控用户轨迹:通过记录用户交互和资源的加载,以及依赖浏览器的错误,构建可视化和收集能优化单页应用的有效视角。
如观测云一样的监控方案能帮助用户清晰看到包括前端应用等技术栈的性能。观测云RUM能自动追踪用户行为的latency,收集关键的元数据,比如浏览器、os、设备和地理信息。同时,使用观测云APM,能够通过请求把前后端串联监控网站的性能。