Navigation Timing API: How to Profile Page Loads Efficiently
Page load time is one of the most important aspects of user experience on the web. When pages load too slowly, users quickly become frustrated and take their business elsewhere. Unfortunately, troubleshooting a slow page load is not typically a straightforward process because many factors contribute to the overall time. For example, a page’s load time can be influenced by the user’s browser, network conditions, server load, and application code, among other things. Thankfully, the Navigation Timing API can help us do so with ease.
As a developer, methods for gathering data on these various factors has been limited in the past. For many developers, the JavaScript Date object has long been the standard for gathering performance data. For example, the following code measures load time by comparing timestamps once the page’s load event handler is invoked.
var start = new Date();
window.addEventListener("load", function() {
var elapsed = (new Date()).getTime() - start.getTime();
}, false);
There are several problems with this approach. First, JavaScript time is notoriously inaccurate. Second, using the Date object introduces overhead and clutters the application code. Third, the Date object can only measure the execution time once the code is running in the browser. The Date object cannot provide any data regarding the page load process involving the server, network, etc.
Cue Navigation Timing API
In order to provide more accurate and comprehensive page load data, the W3C has proposed the Navigation Timing API. The proposed API provides more detailed timing information throughout the page load process. Unlike the Date object, the navigation timing API can provide measurements related to DNS lookup, TCP connection establishment, page redirects, time spent building the DOM, and various other metrics. Navigation timing is also built directly into the browser, meaning that no additional overhead is created.
Detecting Support
The Navigation Timing API is currently only supported in Internet Explorer 9+, Firefox, and Chrome. Therefore, support for the API should be detected before attempting to use it. The API is defined in the window object as “window.performance.timing”. The following function detects whether or not the API is supported.
function supportsNavigationTiming() {
return !!(window.performance && window.performance.timing);
}
Recorded Events
The API records the time when numerous milestones in the page load process occur. Each of these events is stored as a property of the “window.performance.timing” object. The following list describes each event. If a given event does not occur (for example a page redirect), then its value is zero. Note: Mozilla claims that the events occur in this order.
- navigationStart ― This represents 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” (see next item). This is the beginning of the page load time as perceived by the user.
- fetchStart ― “fetchStart” represents 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 “domainLookupStart” value corresponds to 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 ― This value represents the time immediately after the DNS lookup occurs. If a DNS lookup is not required, then the value is the same as “fetchStart”.
- connectStart ― This denotes 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 ― Once the connection to the server is established, the “connectEnd” time is set. If the URL is a cached or local resource, then this value is the same as “domainLookupEnd”.
- secureConnectionStart ― If the HTTPS protocol is used, “secureConnectionStart” is set to the time immediately before the secure handshake begins. If the browser does not support HTTPS, this value should be undefined.
- requestStart ― “requestStart” represents the time just before the browser sends the request for the URL. The API does not define a “requestEnd” value.
- redirectStart ― “redirectStart” represents the start time of a URL fetch that initiates a redirect.
- redirectEnd ― If any redirects exist, “redirectEnd” represents the time after the last byte of the last redirect response is received.
- responseStart ― This corresponds to the time immediately after the browser receives the first byte of the response.
- responseEnd ― This represents the time immediately after the browser receives the last byte of the response.
- unloadEventStart ― This represents 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 ― This represents 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.
- domLoading ― “domLoading” represents the time immediately before the “document.readyState” value is set to “loading”.
- domInteractive ― “domInteractive” corresponds to the time immediately before the “document.readyState” value is set to “interactive”.
- domContentLoadedEventStart ― This represents the time immediately before the DOMContentLoaded event is fired.
- domContentLoadedEventEnd ― This represents the time immediately after the DOMContentLoaded event is fired.
- domComplete ― The “domComplete” value represents the time immediately before the “document.readyState” value is set to “complete”.
- loadEventStart ― This value represents the time immediately before the window’s load event is fired. If the event hasn’t been fired yet, the value is zero.
- loadEventEnd ― This represents the time immediately after the window’s load event is fired. If the event hasn’t been fired, or is still running, then the value is zero.
Navigation Types
The Navigation Timing API also defines an interface for determining how a user landed on a particular page. The “window.performance” object also contains a “navigation” object, which contains two properties — “type” and “redirectCount”. The “type” property provides the method by which the user navigated to the current page. The following list describes the values that “type” can hold.
- If the user navigates to a page by typing a URL, clicking a link, submitting a form, or through a script operation, then the value of “type” is zero.
- If the user reloads/refreshes the page, then “type” is equal to one.
- If the user navigates to a page via history (back or forward buttons), then “type” is equal to two.
- For any other circumstances, “type” is equal to 255.
The “redirectCount” property contains the number of redirects taken to the current page. If no redirects occurred, or if any of the redirects were from a different origin, then “redirectCount” is zero. The following example shows how the navigation data is accessed.
var navigation = window.performance.navigation;
var navType = navigation.type;
var redirectCount = navigation.redirectCount;
Making Sense of the Data
The Navigation Timing API is useful for calculating certain components of page load time. For example, the time taken to perform a DNS lookup can be calculated by subtracting “timing.domainLookupStart” from “timing.domainLookupEnd”. The following example calculates several useful metrics. “userTime” corresponds to the total page load delay experienced by the user. The “dns” and “connection” variables represent the times taken to perform DNS lookup and connect to the server, respectively. The total time taken to send a request to the server and receive the response is stored in “requestTime”. Finally, the total time to complete the document fetch (including accessing any caches, etc.) is stored in “fetchTime”. Notice that the setTimeout() function is called from within the window load event handler. This ensures that the navigation timing data is not used until immediately after the load event finishes. If the timing data were to be accessed from the load event handler, the value of “timing.loadEventEnd” would be zero.
window.addEventListener("load", function() {
setTimeout(function() {
var timing = window.performance.timing;
var userTime = timing.loadEventEnd - timing.navigationStart;
var dns = timing.domainLookupEnd - timing.domainLookupStart;
var connection = timing.connectEnd - timing.connectStart;
var requestTime = timing.responseEnd - timing.requestStart;
var fetchTime = timing.responseEnd - timing.fetchStart;
// use timing data
}, 0);
}, false);
The Navigation Timing API can be used in conjunction with Ajax calls to report actual user data back to a server. This is useful because it allows developers to see how the page behaves for users in the real world. The data can also be used to create a visualization of the page load process. In fact, Google Analytics already incorporates navigation timing into its reports.
Things to Remember
- The JavaScript Date object cannot accurately measure page load data because it has no knowledge of the request prior to running in the browser.
- The Navigation Timing API is built directly into the browser, and provides more detailed timing measurements.
- The API also tracks how users navigate to a page.
- Navigation timing data can be sent to servers for analysis.