Web Performance Calendar

The speed geek's favorite time of year
2012 Edition
ABOUT THE AUTHOR
Bryan McQuade photo

Bryan McQuade (@bryanmcquade) leads the Page Speed team at Google. He has contributed to various projects that make the web faster, including Shared Dictionary Compression over HTTP and optimizing web servers to better utilize HTTP.

Over the past few years, we’ve made great strides in understanding and optimizing mobile web performance. However, for the most part, mobile web browsing continues to be slow. Google Analytics data shows that the average web page takes over 10 seconds to load on mobile. We know that a user’s thought process is typically interrupted after waiting for just one second, resulting in that user starting to become disengaged. So at a minimum, the “above the fold” content of a web page should render in less than one second. Clearly, we’ve still got a lot of work to do.

But where should we focus our attention when optimizing mobile web performance? We know that mobile networks have highly variable latency and bandwidth characteristics, and that in general mobile network latency is substantially higher than that on desktop connections. We also know that for modern networks, it is round trip time, not bandwidth, that is the dominating factor in page load time. Given this, to make the mobile web fast, our attention should be focused on minimizing the number of blocking round trips incurred before a web page can render its content to the device’s screen.

What blocks rendering a web page to the screen?

First, let’s look at the sequence of events that happens between the time a user initiates a page navigation and the time the browser can render that page to the screen. Round trips may be incurred for DNS resolution, TCP connection, and the request being sent to the server and the response being streamed back. Unfortunately, there’s not much developers can do to avoid these round trips. For repeat visitors, a longer DNS TTL can help, but TCP connection and request/response overhead will be incurred on every new navigation to a page (assuming there is no warm TCP connection ready to be reused by the client).

Once these initial round trips are incurred, the mobile device can begin parsing the HTML response. But the browser can’t paint content to the screen just yet. Before content in the HTML can be painted to the screen, the browser must construct the render tree to determine where the elements in the DOM will appear on screen. And before the render tree can be constructed, the DOM tree must be constructed. The DOM tree is constructed through a combination of parsing HTML and possibly JavaScript execution.

So what are the things that block parsing of HTML, DOM tree construction, and render tree construction? Most of the time, parsing, DOM tree, and render tree construction are very fast. However, there are a few antipatterns that can cause these processes to get blocked on the network.

Sources of delay during rendering: external JavaScript and CSS

The most significant source of delay during HTML parsing is external JavaScript. When a browser encounters a (non-async) external script during HTML parsing, it must halt parsing of subsequent HTML until that JavaScript is downloaded, parsed, and executed. This incurs additional round trips, which are especially expensive on mobile. If the script is loaded from a hostname other than the hostname the HTML was served from, additional round trips may be incurred for DNS resolution and TCP connection.

In addition, render tree construction gets blocked on stylesheets, so just as external JavaScript introduces delays during DOM tree construction, external stylesheets introduce delays during render tree construction.

In short, external JavaScript and CSS loaded early in the document (e.g. in the <head>) are performance killers, and they are especially expensive on mobile due to the higher round trip times associated with mobile networks.

Making mobile pages fast

To be fast, a mobile web page must include all of the content needed to render the above the fold region in the initial HTML payload without blocking on external JavaScript or CSS resources. Ideally, all the content needed to render the above the fold region should be in the first 15kB on the network (this is post-gzip-compression size; pre-gzip can be larger), since this is the size of the initial congestion window on modern Linux kernels. This does not mean simply inlining all of the JavaScript and CSS that used to be loaded externally. Instead, just the JavaScript and CSS needed to render the above the fold region should be inlined, and JavaScript or CSS needed to add additional functionality to the page should be loaded asynchronously. For instance, if we have a page like the following:

<html>
<head>
  <link rel="stylesheet" href="my.css">
  <script src="my.js"></script>
</head>
<body>
  <div class="main">
    Here is my content.
  </div>
  <div class="leftnav">
    Perhaps there is a left nav bar here.
  </div>
  ...
</body>
</html>

We need to identify the parts of my.js and my.css needed to render the initial content, inline those parts, and delay or async load the remaining JavaScript and CSS needed for the page. This may end up looking something like:

<html>
<head>
  <style>
  .main { ... }
  .leftnav { ... }
  /* ... any other styles needed for the initial render here ... */
  </style>
  <script>
  // Any script needed for initial render here.
  // Ideally, there should be no JS needed for the initial render
  </script>
</head>
<body>
  <div class="main">
    Here is my content.
  </div>
  <div class="leftnav">
    Perhaps there is a left nav bar here.
  </div>
  ...
  <!-- 
    NOTE: delay loading of script and stylesheet may best be done
     in an asynchronous callback such as `requestAnimationFrame` 
     rather than inline in HTML, since the callback will be invoked 
     after the browser has rendered the earlier HTML content to the screen.
   -->
  <link rel="stylesheet" href="my_leftover.css">
  <script src="my_leftover.js"></script>
</body>
</html>

The current state of the mobile web

A brief survey of mobile web pages shows that nearly all pages include blocking external JavaScript and/or CSS before any above the fold content is displayed. Exceptions include Google Maps, Google Search, Yahoo! News, and sites like http://www.yummly.com/ and Kayak. Unfortunately, some of these sites inline JavaScript and CSS that isn’t needed for the initial render, which unnecessarily delays the time it takes to render these pages.

Interestingly, browsing the mobile web with JavaScript disabled reveals that even though most pages load blocking external JavaScript in the head, few of these pages actually need that JavaScript to render their initial content. These pages would benefit from delay or async loading their JavaScript to get the JavaScript out of the critical path of the initial render of the page.

What else can block the initial render of a web page?

Blocking external JavaScript and CSS are the most common sources of delay on the web. However, there are other common sources of delay that mobile developers should be aware of. One source is HTTP redirects of the main HTML document. These redirects incur additional round trips, and if the redirect navigates to a different hostname (e.g. www.example.com to m.example.com), they add even greater delays due to DNS resolution and TCP connection times. A second source of delay is server backend time spent generating the HTML response. All of the time spent generating the initial HTML response will block rendering on the screen, so server backend time should be kept to a minimum.

Rendering in under one second

If we estimate 3G network round trip time at 250ms, we can compute the minimum estimated time between when a user initiates a web page navigation and when that page renders its above the fold content on the screen. Assuming no blocking external JavaScript or CSS, we incur three round trips for DNS, TCP, and request/response, for a total of 750ms, plus 100ms for backend time. This brings us to 850ms. As long as render-blocking JavaScript and CSS is inlined and the size of the initial HTML payload is kept to a minimum (e.g. under 15kB compressed), the time it takes to parse and render should be well under 100ms, bringing us in at 950ms, just under our one second target.

Summary

In summary, to make your mobile web page render in under one second, you should:

  • keep server backend time to generate HTML to a minimum (under 100ms)
  • avoid HTTP redirects for the main HTML resource
  • avoid loading blocking external JavaScript and CSS before the initial render
  • inline just the JavaScript and CSS needed for the initial render
  • delay or async load any JavaScript and CSS not needed for the initial render
  • keep HTML payload needed to render initial content to under 15kB compressed

If you are looking to improve the performance of your mobile web pages, give these optimizations a try.