Measuring the performance of single page applications (SPAs) presents some unique challenges. Philip Tellis and Nicholas Jansma deep-dived into the subject at the Velocity conference in Amsterdam, providing context and specific advice on how to measure performance for that kind of web applications.
Tellis is the author of the popular boomerang library and Chief Architect at SOASTA. Jansma is Senior Software Engineer at SOASTA, a vendor of performance-related products.
Tellis and Jansma distinguish between hard navigation and soft navigation. Hard navigation is the first page load of an SPA, including rendering the initial route. Soft navigation is any subsequent route change.
To measure anything, we need to know when the start and end events occur. This is a challenge for single page applications, due to the soft navigations nature. The onload
event no longer matters, as most activities occur after that event. Soft navigations are not real navigations, that is, you can change a browser's URL without really navigating and thus, without triggering onload
events. And the browsers don't have an API that informs if and when all the resources - i.e.: images, scripts, CSS, videos - have been downloaded. Knowing when to mark the start and end events of a soft navigation, in an automated fashion, requires heuristics.
A new soft navigation may be detected in several ways. The browser history may change. SPA frameworks may trigger specific routing events. For instance, AngularJS triggers $routeChangeStart
. The user may click on some DOM element. Or a XHR (XmlHttpRequest) may trigger DOM changes. Any one of these events does not signal a soft navigation per se. It all depends on subsequent events that may or may not involve resource fetching, significant DOM updates or browser history changes.
What makes up the end event of a soft navigation depends on context. It may be the end of all resource fetching, the ability of the user to interact with the page or other event entirely. Tellis and Jansma recommend monitoring network activity, by intercepting both XHR requests and implicit resource loading via DOM elements, such as img
tags. Boomerang provides the auto_xhr plugin that handles both scenarios. The plugin proxies the XHR object to monitor server requests and also takes advantage of the Mutation Observer to monitor the relevant DOM changes (i.e.: src
and href
attribute changes) that signal resource fetching.
It's important to measure not only the downloading of resources, but also the time the browser takes to render the changes (e.g.: rendering an image). Applying a setTimeout(..., 0)
allows the browser to render before measuring the performance:
var xhr = new XMLHttpRequest();
xhr.open("GET", "fetchstuff");
xhr.addEventListener("load", function(){
$(document.body).html(xhr.responseText);
setTimeout(function() {
var endTime = Date.now();
var duration = endTime - startTime;
}, 0);
});
var startTime = Date.now();
xhr.send();
This complicated state-of-affairs may change in the future. The WHATWG (Web Hypertext Application Technology Working Group) is working on the Fetch standard, which aims to unify resource fetching by providing a consistent API.
The User Timing API is just a standardized API for custom performance measurement. This API provides the best way to do meaningful, business transaction-aware measurements. On the downside, it requires custom coding for each metric.
For hard navigations, browsers already provide several timing-related API's. The Navigation Timing API gives access to detailed information related to hard navigations. The Resource Timing API provides information about resource load timing but, crucially, does not offer any way to know if the browser is loading resources. These APIs aren't geared towards single page application contexts.
The slides of this talk can be found at the SlideShare.