Blog

A

How-To: Provide Real User Monitoring for Single-Page Applications

Wed, May 20th, 2015 | Nic Jansma

Apps built with AngularJS provide an interesting challenge for real user monitoring (RUM) solutions. Here’s how we overcame those challenges at SOASTA.

AngularJS is a modern web development framework that is fast becoming one of today’s most popular tools for web developers. Sites built with frameworks like AngularJS live in the category of “single-page applications” (SPAs). One of the core differentiators of SPAs over “traditionally” built websites is that they run on a single page, dynamically bringing in content as necessary. (Other notable SPA frameworks include Ember.jsBackbone.js and React.)

Apps built with AngularJS present three major challenges.

After the AngularJS framework has loaded and displayed the desired page, subsequent interactions that would have traditionally navigated the browser to a new URL no longer do so. The framework is responsible for fetching any new content on the fly and dynamically showing it to the user. RUM solutions that measure page loads do not have insight into these in-page navigations (sometimes called soft navigations).

We’re seeing a growing need to be able to monitor today’s latest breed of websites built with SPA frameworks such as AngularJS. To do so, we’ve made improvements to Boomerang, which is responsible for gathering performance metrics for mPulse.

There are three major challenges when trying to accurately gather RUM performance metrics for AngularJS:

Challenge #1: The onload event no longer matters.

The first issue is that the onload event no longer matters for SPAs.

When visitors navigate to a web app for the first time, the browser begins to download all of the JavaScript, CSS and other resources that are statically included in the HTML. If the site was built with AngularJS, this means downloading and executing the angular.js JavaScript framework as well.

After the browser has finished downloading all of the statically included resources, it will fire the body’s onloadevent, which is the traditional signal that the page load has completed.  This is also the event that most performance tools hook into to measure page load performance.

The problem with the onload event is that it only waits for statically included resources. After the angular.js JavaScript file was downloaded and executed, the AngularJS framework boots up and decides which content it will show to the visitor. AngularJS then fetches any external resources (if necessary), and dynamically injects these resources into the page.

The body onload event does not wait for any of these dynamically injected resources. Thus measuring when the onload event fires is only measuring up to the point that the web app’s core HTML and angular.js finished downloading — much earlier than when the page could be interacted with by the user.

Depending on how much content the AngularJS app is displaying to the user, that point of interaction could be up to several seconds after the onload event.

Here’s a visual representation of this challenge:

graph

The above waterfall is from WebPagetest of the AngularJS Sample App. The browser fires the onload event at 1.225 seconds (in the middle of the image, near the purple vertical bar), while the majority of the visual resources (.jpgs) don’t start downloading until after 1.5 seconds or so. The WebPagetest filmstrip shows that nothing is displayed to the user until about 1.7 seconds and the page doesn’t appear visually complete until about 1.9 seconds:

graph

The onload event just isn’t useful for single-page applications.  In the above example, onload fires 700 milliseconds before the user sees any content.

Challenge #2: Soft navigations are not real navigations.

The second issue is that SPAs do not trigger a new browser navigation.

Once the first page has loaded, a visitor to an AngularJS app will be navigating around the content. Each of the links clicked tell AngularJS to fetch content and dynamically swap it into the current view (soft navigations). As a result, the browser is no longer doing a traditional “navigation”, where it tears down the old page, fetches the new HTML, and builds the new page up from scratch.

This is great news for the performance of SPAs. The browser is no longer spending time re-rendering the same header and footer that’s on every page of the site. It is no longer re-parsing the HTML, JavaScript, and CSS on every navigation. SPA frameworks are a much more efficient way of building web apps.

The challenge is that soft navigations leave traditional performance analytics tools in the dark as they assume a page load happens only once per browser navigation.

 

Challenge #3: The browser won’t tell you when all resources have been downloaded.

 

The third issue is a side effect of the first two issues: the browser no longer fires an onload event after the first navigation, so subsequent soft navigations don’t fire the onload event again to tell the page when all of the resources have been fetched.

Every time a web app’s visitors are navigating, the app is likely initiating multiple networking calls to fetch new HTML, JavaScript, JSON, CSS, Images, XHRs, ads, etc. For traditional websites, since these are complete navigations, the browser is monitoring all of these downloads and firing the body onload event once they are complete.

In a soft navigation scenario, the browser does not fire the onload event again. AngularJS itself fires a couple events when the page navigates (such as $routeChangeStart and $viewContentLoaded), but these events are only when the new HTML is swapped into the page, not when the downloads are complete.

Thus AngularJS’s events alone are not a good corollary to the traditional onload event.

Our solution

Boomerang, the JavaScript that gathers performance metrics for mPulse, SOASTA’s real-user monitoring solution, has been updated to work with AngularJS.

Two major improvements were made to address the three challenges above:

  • First, we now listen for AngularJS routing events, such as $routeChangeStart. Once we see that the visitor is about to view new content, we start listening in preparation for gathering performance metrics for the soft navigation.
  • Second, and most importantly, when the page is first loading, and when a soft navigation starts, we begin monitoring the HTML document for any new downloadable resources that get inserted. We do this via a MutationObserver.

For example, take the AngularJS Sample App.  It sends an XHR to gather details about all of the products, which contain links to those product images.  All of these <IMG>s are inserted into the document by an AngularJS template, and the browser starts downloading them.

Remember that, after the initial page load, the browser will no longer be firing an onload event, so we do not have a way to know when all of these images have been downloaded unless we can monitor them on our own. Via the MutationObserver, we hook into any new images, JavaScript, CSS and IFRAMEs’ load events to know when they have been successfully downloaded. It’s only once all of these resources have been fetched that we call the soft navigation complete.

Here’s an annotated timeline that shows how the MutationObserver helps us monitor all of the resources initiated by AngularJS:

graph

Once you have the correct timeframe for the soft navigation, you may also want to gather performance information for all of the relevant resources that were fetched (via ResourceTiming). One problem is that the ResourceTiming buffer is fixed by default to 150 entries. A soft navigation doesn’t clear the buffer, so you may want to increase it yourself, or intelligently clear it on every soft navigation.  We’ve posted some instructions on how to do this in our documentation section.

We’ve built this solution into Boomerang, which is available for all SOASTA customers. The changes will be making their way to the open-source Boomerang shortly as well.

Instructions for using Boomerang with AngularJS in our documentation section.