Improving Site Performance with the Navigation Timing API

navigation_timing_header

The web is evolving at a crazy pace. Every day new frameworks, tools, and libraries are released with the ambition, if not the potential, to become the next jQuery. As a developer, I feel that sometimes it’s really hard to stay up-to-date with all the new software, techniques and practices introduced by top-notch developers and designers out there. Nonetheless, this trend seems to be driving the web platform forward on issues like performance, security, and accessibility.

In part because of these efforts, organizations like the W3C are pushing toward the creation, the standardization, and the adoption of new APIs focused on these areas, in particular performance. Over the past few years, a lot of new JavaScript APIs have been proposed and implemented by browsers to help developers gauge and improve the performance of web applications. For example, you can use the User Timing API to accurately measure the performance of a snippet of code by having access to high precision timestamps, and the Timing Resource API to collect complete timing information related to resources in a document.

However, when it comes to performance, the loading time of a page is an especially important aspect of the overall user experience. If a web page loads too slowly, users quickly become frustrated and are more likely to abandon the page. Hence, your business loses potential customers and revenue. The loading time of a page can be influenced by many factors such as the network speed, the server load, the user latency, and the performance of the code of the page.

In this article I’ll introduce you to a new JavaScript API, called the Navigation Timing API, that will help you in measuring the performance of your web pages.

What’s the Navigation Timing API?

The Navigation Timing API provides Web applications with timing-related information. This API exposes several properties that offer information about the time at which certain events happen, like the time immediately before the DNS lookup for the URL occurs. It does not, however, provide any methods or events to listen to.

This API is a W3C Recommendation which means that its specifications are set in stone and won’t change in the future unless a new version is released. This means that this is an API you can start using today.

Using the Resource Timing API allows us to retrieve and analyze a detailed profile of all the network timing data for a given page. Once you retrieve the data using this API, you can send them to your server using an Ajax call. Doing so, you can understand if the page has issues that need to be addressed.

Developers have have become accustomed to measuring the loading time of a page using code similar to what is shown:

<!doctype html>
<html>
   <head>
      <script>
         var start = new Date().getTime();
         window.onload = function() {
            var end = new Date().getTime();
            console.log('Loading time (in milliseconds): ' + (end - start));
         }
      </script>
   </head>
   <body>
   </body>
</html>

Although very simple, the code above has several issues. The first one is that JavaScript time is notoriously inaccurate and is skewed by adjustments to the system clock. In addition, the Date object can only measure the execution time once the code is running in the browser. It can’t provide any data regarding the page load process involving the server, network, and so on.

Using the Navigation Timing API we can obtain a more detaied measure of the user’s perceived loading time as shown below:

<!doctype html>
<html>
   <head>
      <script>
         window.addEventListener('load', function() {
            var now = new Date().getTime();
            console.log('Perceived loading time (in milliseconds): ' + (now - performance.timing.navigationStart));
         });
      </script>
   </head>
   <body>
   </body>
</html>

Now that we know what this API is, let’s delve into its properties.

Properties

The Navigation Timing API is exposed through the timing property of the window.performance object. The events measured are offered as properties of timing. Below you find a list of them in the order they happen:

  • navigationStart: The time immediately after the browser finishes prompting to unload the previous document. If there is no previous document, then navigationStart is equal to fetchStart. This is the beginning of the page load time as perceived by the user.
  • unloadEventStart: The time immediately before the previous document’s unload event is fired. If there is no previous document, or if the previous document is from a different origin, then this value is zero.
  • unloadEventEnd: The time immediately after the previous document’s unload event is fired. If there is no previous document, or if the previous document is from a different origin, then this value is zero. If there are any redirects that point to a different origin, then unloadEventStart and unloadEventEnd are both zero.
  • redirectStart: The start time of a URL fetch that initiates a redirect.
  • redirectEnd: If any redirects exist, this is the time after the last byte of the last redirect response is received.
  • fetchStart: The time immediately before the browser begins searching for the URL. The search process involves checking application caches or requesting the file from the server if it is not cached.
  • domainLookupStart: The time immediately before the DNS lookup for the URL occurs. If no DNS lookup is required, then the value is the same as fetchStart.
  • domainLookupEnd: The time immediately after the DNS lookup occurs. If a DNS lookup is not required, then the value is the same as fetchStart.
  • connectStart: The time immediately before the browser connects to the server. This value is equal to domainLookupEnd if the URL is a cached or local resource.
  • connectEnd: The time the connection to the server is established. If the URL is a cached or local resource, then this value is the same as domainLookupEnd.
  • secureConnectionStart: If the HTTPS protocol is used, this is the time immediately before the secure handshake begins; otherwise this value is undefined.
  • requestStart: The time before the browser sends the request for the URL.
  • responseStart: The time immediately after the browser receives the first byte of the response.
  • responseEnd: The time immediately after the browser receives the last byte of the response.
  • domLoading: The time immediately before the document.readyState value is set to loading.
  • domInteractive: The time immediately before the document.readyState value is set to interactive.
  • domContentLoadedEventStart: The time immediately before the DOMContentLoaded event is fired.
  • domContentLoadedEventEnd: The time immediately after the DOMContentLoaded event is fired.
  • domComplete: The time immediately before the document.readyState value is set to complete.
  • loadEventStart: The time immediately before the window’s load event is fired.
  • loadEventEnd: The time immediately after the window’s load event is fired.

By using these values, you can gauge certain components of the page load time. For example, you can measure the time spent to perform a DNS lookup by subtracting domainLookupStart from domainLookupEnd.

The following image taken from the official specifications offers a graphical representation of the properties I just described. Those that are underlined may not be available when fetching resources from different origins:

The timing attributes defined by the PerformanceTiming interface and the PerformanceNavigation interface

The timing attributes defined by the PerformanceTiming interface and the PerformanceNavigation interface

In addition to the previous set of properties, the Navigation Timing API also defines another object to determine how a user landed on a particular page. This object is called navigation and belongs to the window.performance object as well. It provides the following properties:

  • type: The method by which the user navigated to the current page. This property can assume one of the following values: 0, 1, 2, 255. A value of zero means the user landed by typing a URL, clicking a link, submitting a form, or through a script operation. A value of one means the user reloaded the page. A value of two means the user landed on the page via history (back or forward buttons). A value of 255 is an umbrella value for any other reason.
  • redirectCount: The number of redirects taken to the current page. If no redirects occurred, or if any of the redirects were from a different origin, this value is zero.

Now that Ive bored you enough with extremely long lists of properties, let’s take a look at which browsers support this API.

Browsers Support

Browser support for this API is very good on both desktop and mobile. On desktop, the Navigation Timing API has been implemented in Chrome 6+, Firefox 7+, Internet Explorer 9+, and Opera 15+. So, the only desktop browser that does not currently support this API is Safari. On mobile, the situation is very similar, with the addition of iOS Safari 8+, and Android Browser 4+, Blackberry Browser, and UC Browser for Android. Therefore, Opera Mini is the only mobile browser that does not currently support this API.

Despite the fact that this API is widely supported, it’s still advisable to adhere to the principles of the progressive enhancement and always test for support. To do that, you can employ the code below:

if ( !('performance' in window) ||
     !('timing' in window.performance) ||
     !('navigation' in window.performance)
) {
  // API not supported
} else {
   // API supported
}

Demo

Let’s build a simple demo that allows us to see this API in action and the information it provides. First, we check whether the Navigation Timing API is supported or not. If the API isn’t supported, we display the message “API not supported.” Otherwise the code displays all the timing details provided by the API as soon as the load event of the window object is fired.

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
      <meta name="author" content="Aurelio De Rosa">
      <title>Navigation Timing API Demo by Aurelio De Rosa</title>

      <style>
         *
         {
            -webkit-box-sizing: border-box;
            -moz-box-sizing: border-box;
            box-sizing: border-box;
         }

         body
         {
            max-width: 500px;
            margin: 2em auto;
            padding: 0 0.5em;
            font-size: 20px;
         }

         h1
         {
            text-align: center;
         }

         .hidden
         {
            display: none;
         }

         .value
         {
            font-weight: bold;
         }

         .author
         {
            display: block;
            margin-top: 1em;
         }
      </style>
   </head>
   <body>
      <h1>Navigation Timing API</h1>

      <span id="nt-unsupported" class="hidden">API not supported</span>

      <h2>Timing info</h2>
      <ul id="timing-list">
      </ul>

      <h2>Navigation info</h2>
      <ul id="navigation-list">
      </ul>

      <small class="author">
         Demo created by <a href="http://www.audero.it">Aurelio De Rosa</a>
         (<a href="https://twitter.com/AurelioDeRosa">@AurelioDeRosa</a>).<br />
         This demo is part of the <a href="https://github.com/AurelioDeRosa/HTML5-API-demos">HTML5 API demos repository</a>.
      </small>

      <script>
         if ( !('performance' in window)            ||
              !('timing' in window.performance)     ||
              !('navigation' in window.performance)
         ) {
            document.getElementById('nt-unsupported').className = '';
         } else {
            window.addEventListener('load', function() {
               var list = '';
               var timings = window.performance.timing;
               for(var timing in timings) {
                  list += '<li>' + timing + ': <span class="value">' + timings[timing] + '</span></li>';
               }
               document.getElementById('timing-list').innerHTML = list;

               list = '';
               list += '<li>redirectCount: <span class="value">' + window.performance.navigation['redirectCount'] + '</span></li>';
               list += '<li>type: <span class="value">' + window.performance.navigation['type'] + '</span></li>';
               document.getElementById('navigation-list').innerHTML = list;
            });
         }
      </script>
   </body>
</html>

You can view the code live here.

Conclusion

Ensuring your site loads quickly is a major concern for developers today. The Navigation Timing API is one of the several APIs you can employ but, as you’ve learned in this tutorial, it can provide a high level of detail while being very easy to use. Finally, the support for this API is very good among desktop and mobile browsers, which menas that you can start using it today.

Header image courtesy of Martin Fisch

Comments