Google Analytics goes async

December 1, 2009 4:36 pm | 55 Comments

Today’s announcement that Google Analytics Launches Asynchronous Tracking is music to my ears. Not only does it make web sites faster, switching over to this async pattern improves uptime and increases the amount of analytics data gathered. I’ll touch on each of these three benefits, and wrap-up with an overview of the new code snippet.

Faster

The pain of loading JavaScript files is that they block the page from rendering and block other resources from downloading. There are workarounds to these problems. Chapter 4 of Even Faster Web Sites describes six techniques for Loading Scripts Without Blocking. One of those, the Script DOM Element approach, is the technique used in the new Google Analytics async pattern. Google Analytics’ ga.js file is a perfect example of a script that should be loaded asynchronously – it doesn’t add any content to the page, so we want to load it without blocking the images and stylesheets that give users what they really came to see.

Improved Uptime

What happens if a script takes a long time to load, or fails to load? Because scripts block rendering, users are left staring at an empty page. Google Analytics has an amazing infrastructure behind it, but any resource, especially from third parties, should be added cautiously. It’s great that the GA team is evangelizing a pattern that allows the web site to render while ga.js is being downloaded.

More Data

One workaround to the blocking problem is to move scripts to the bottom of the page. In fact, this is exactly what’s suggested in the old ga.js snippet. But this means users who leave a page quickly won’t generate any analytics data (they leave before the script at the bottom finishes loading). Moving to the async pattern and loading it at the bottom of the page’s head, as suggested, means more of these quick page views get measured. This is too good to believe – not only do you get a faster, more resilient page, but you actually get better insights into your traffic.

The Async Snippet

Just to be clear, ga.js will continue to work even if web site owners don’t make any changes. But, if you want a faster site, greater uptime, and more data, here’s what the new async snippet looks like:

var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);

(function() {
var ga = document.createElement('script');
ga.src = ('https:' == document.location.protocol ?
    'https://ssl' : 'http://www') +
    '.google-analytics.com/ga.js';
ga.setAttribute('async', 'true');
document.documentElement.firstChild.appendChild(ga);
})();

It’s extremely cool to see this pattern being evangelized for such a major piece of the Internet. A few items of note:

  • Obviously, you have to replace “UA-XXXXX-X” with your ID.
  • Since ga.js is being loaded asynchronously, there has to be a way for web site owners to couple their desired GA functions with the code when it finishes loading. This is done by pushing commands onto the Google Analytics queue object, _gaq.
  • Once all your callback commands are queued up, the ga.js script gets loaded. This is wrapped inside an anonymous function to avoid any namespace conflicts.
  • Inside the anonymous function is where we see the Script DOM Element approach being used – with two nice improvements. A ‘script’ element is created and its SRC is set to the appropriate ga.js URL. Looking ahead to support of asynchronous scripts in HTML5, the ‘async’ attribute is set to ‘true’. Very nice! The main benefit of this is it tells the browser that subsequent scripts can be executed immediately – they don’t have to wait for ga.js. The last line adds the script element to the DOM. This is what triggers the actual download of ga.js. In most of my code I do document.getElementsByTagName(“head”)[0].appendChild, but that fails if the document doesn’t have a head element. This is a more robust implementation.

It’s always hard to find the right spot on the complexibility curve. This async snippet hits it just right. It’s slightly more complex than the old pattern, but not by much. Besides the benefits highlighted here, this new pattern is able to support more advanced usage patterns, including pushing an array of commands and pushing functions.

The theme driving much of my work this year is fast by default. I want high performance to be baked into the major components of the Web, so things are just fast. Seeing Google Analytics adopt this high performance async pattern is a huge win. But the proof is in the pudding. If you switch over to the new async pattern, measure how it affects your page load times and the amount of data gathered, and add a comment below. My prediction: 200ms faster and 10% more data. What do you see?

55 Responses to Google Analytics goes async

  1. Google seems to be using two different methods for faster web sites. The gmail team is commenting out script within the tags.

    Which is faster and are there recommendations on when to use either approach?

  2. I say: rad!!

  3. This is a great step in the right direction! Good job GA team.

    Unfortunately it can only help those who actually use it. I’m concerned because there there is still a large chuck of GA users out there who are still using the legacy urchin.js tracking code, let alone the blocking ga.js code. (40% according to Pingdom). I’d love to see a push by the GA to notify those users some in the GA dashboard. I’m thinking something along the lines of the “please upgrade to a faster browser” message that Google displays to IE6 users.

    http://royal.pingdom.com/2009/04/08/40-still-use-old-google-analytics-script/

  4. Sweet, this is a very welcome change. It’s always been a hassle deciding whether to push Analytics to an iframe, push it to after the DOM is loaded, or just risk the occasional delays.

    Interesting that you can push your own functions, too. I’ll have to try adding Brian Cray’s outbound link tracker in there.

  5. what shall we do rows below within new version ?

    pageTracker._setCampContentKey(“XXX”);
    pageTracker._setCampMediumKey(“XXX”);
    pageTracker._setCampNameKey(“XXX”);
    pageTracker._setCampSourceKey(“XXX”);
    pageTracker._setCampTermKey(“XXX”);
    pageTracker._setDomainName(“XXX”);
    pageTracker._trackPageview(“XXXX”);

  6. Actually I don’t see here a lot of improvements.

    1) If this async chunk of data will be loaded somewhere in the beginning of the page — it will take one channel and block some other resources to be loaded.

    2) If this chunk will be loaded somewhere in the body of the page — this increases propability of not gathered data (2 scripts will be loaded async -> so both will be being loaded longer than 1 -> so GA will gather data with more delay if it has simple block behavior).

    Maybe GA team has some more reasons to make this change, but I think there should be a number of possible integration ways for website owners to choose from – to provide accurate data, or load page fast, or something else. Both 2 things are impossible :)

    P.S. It’s great that Google implement Unobtrusive JavaScript this way but I don’t see here any breakthrough.

  7. It’s obviously a HUGE improvement over the default document.write() mess from before. And if GA is the only script you are loading onto your page, I think this is probably the best answer.

    But, if you are loading a couple (or several) scripts, and you want to take advantage of simple async loading of scripts, you can use LABjs. http://labjs.com

    The simple code to load GA with LABjs would be this:

    $LAB
    .script((‘https:’ == document.location.protocol ? ‘https://ssl’ : ‘http://www’) + ‘.google-analytics.com/ga.js’)
    .wait(function(){
    pageTracker = _gat._getTracker(“UA-XXX-XX”);
    pageTracker._trackPageview();
    });

  8. Of course this will work, but actually is NOT permitted in the HTML5 specs.

    In the spec at http://www.w3.org/TR/html5/semantics.html#attr-script-async it says clearly that the async attribute is a “boolean attribute”, which is further described at http://www.w3.org/TR/html5/infrastructure.html#boolean-attribute as:

    “If the attribute is present, its value must either be the empty string or a value that is an ASCII case-insensitive match for the attribute’s canonical name, with no leading or trailing whitespace.”

    Which means, that the line should be changed to:
    s.setAttribute(“async”, “async”);
    to make it valid HTML5.

  9. @rakesh @steve — it would seem google’s use of “async” is more to tell the library to look for the “gaq” queue and execute it, then it is for HTML5 compliance. Right?

  10. Hi,

    Rakesh has a good point about setting the correct value for the async attribute.

  11. Now that we are able to load the GA snippet in the document head… Is there any reason why I can’t just append the new async snippet to the end of my ‘master.js’ file, which is referenced in the bottom of the document head by an external script tag?

  12. @Steve:
    “that fails if the document doesn’t have a head element. This is a more robust implementation.”

    Actually, there’s a problem with document.documentElement.firstChild.appendChild(ga)… in my XHTML documents (at least) document.documentElement.firstChild is a text node, not an element, so the appendChild operation fails. Don’t HTML parsers automatically insert a HEAD element into the DOM even when one isn’t specified in the markup? Wouldn’t document.getElementsByTagName(“head”)[0].appendChild(ga) still be a better approach?

  13. I couldn’t be happier to see Google implement this. However, I do think this might be slightly motivated by an upcoming algorithm change to sink sites that are slow to load. Still, whatever the motivation, I’m happy to have a faster loading website.

    Josh

  14. We seem to intermittently hit this problem with IE8:
    http://support.microsoft.com/kb/927917

    The problem goes away when the cache is cleared.

  15. http://yura.thinkweb2.com/jstests/document_head_test_by_Garrett.html

    Try that in certain browsers (like Chrome and Opera 10), and you’ll see that in fact the comment node is the first node returned (which is of course invalid to nest a <script> element under).

    My feeling is the GA code is *NOT* safe as is. They should use a more robust option.

  16. @Billy Hoffman – We’re working on something like this.

    @orhan t – Please see our migration guide:

    http://code.google.com/apis/analytics/docs/tracking/asyncMigrationExamples.html

    @Rakesh Pai – We’re investigating. You may be correct. We will fix if so.

  17. @David: The Gmail mobile pattern is for code that might not be used in the page. In this case, the web site owner knows they want ga.js to be loaded.

    @Billy: Hi! Yes, we definitely need to evangelize this new pattern. Right now it’s “beta”, but once it’s final your idea of notifying people through the GA dashboard is good.

    @sunnybear: Loading ga.js earlier takes a channel going to google-analytics.com – other channels are still open. Because of this, other scripts in the page can be downloaded as well, without any blocking.

    @Rakesh: Great tip! I’ll feed this back to the GA team.

    @Kyle: Setting “async” to true is complex to explain: Suppose my page has ga.js and mycode.js. Even if ga.js is downloaded in asynchronously, some browsers (Firefox and Opera) will execute the files in that order. In those browsers, the execution of mycode.js is therefore blocked until ga.js is done downloading and executing. Setting “async” to true tells browsers they can execute mycode.js as soon as possible, independent of ga.js. Firefox should have support for async soon.

    @Weston: I don’t think HEAD is inserted automatically across all browsers. I’ll forward this to the GA team for further investigation.

    @orhan, @Mike V – I’ll ask the GA team to respond to your specific questions.

  18. Thanks for the heads up. I can’t wait to get improved stats from GA. My content manager was all “do we really need to do this?” lol, yes, we do. you can start tomorrow :D

  19. As Kyle have already mentioned, `document.documentElement.firstChild` is not a very robust way to retrieve HEAD element. In browsers like Opera, newer Safari, Chrome (and Blackberry, IIRC), `firstChild` might as well be a comment node. Appending a script element to such node would obviously result in an error (and failure to execute script in question).

    It would be better to use `getElementsByTagName` to get HEAD, and maybe also fall back to `document.documentElement` (present in all but the most ancient browsers) in case HEAD element is missing (IIRC, this can happen in Safari 3.x and few other browsers):

    (document.getElementsByTagName(‘head’)[0] || document.documentElement).appendChild(ga);

    I was also a bit surprised to see script element lack “type” attribute (with “text/javascript” value). “type” is actually required in HTML4, but not in HTML5, from what I can see. In practice though, most browsers understand type-less script elements just fine, so it’s probably not a big deal.

    And speaking of “async” attribute, does anyone know why a shorter (and faster) property-based setter was not employed — `ga.async = true;` ? Just like we often use `scriptElement.type = ‘text/javascript’` and `scriptElement.src = ‘…’`, I don’t see a reason to prefer `setAttribute` here.

  20. As I also commented on this post:
    http://googlecode.blogspot.com/2009/12/google-analytics-launches-asynchronous.html

    … the async property can have two values: “async” or “” to mean TRUE. Anything other value will also be interpreted as true but is not valid according to the spec. The only way for it to be false is to remove the attribute entirely (or just don’t add it when creating or declaring the element).

    Example:
    ga.setAttribute(“async”, “”); // legal and true
    ga.setAttribute(“async”, “async”); // legal and true
    ga.setAttribute(“async”, “true”); // illegal but true
    ga.setAttribute(“async”, “false”); // illegal but true
    ga.removeAttribute(“async”); // legal and false

    Reference:
    http://www.w3.org/TR/html5/infrastructure.html#boolean-attribute

  21. I ended up using this code as a failsafe for antique browsers and broken pages – document.body has been around for ages:

    var container = document.getElementsByTagName(“head”)[0] || document.body;
    container.appendChild(ga);

    You can see the actual source used in my fork of the Django GA app here:
    http://github.com/acdha/django-google-analytics/commit/b2b5a7c840b7b9ac5dc38c6745e883b2e3fbadbe

  22. Hmm, the problem I mentioned has returned even after the cache is cleared, and I’ve been able to get it to occur using IE7 also.

  23. It seems to typically occur the first time the site is accessed after opening IE.

  24. For those of us running multiple GA accounts on a single page, it strikes me that using a generic scripted DOM handler with callbacks (i.e. your prior generic recommendation for async loading) is more efficient because the js file only needs to be evaluated once. with a callback, you can sequence multiple inits after evaluating the file once, which you can’t do with this new anonymous function.

    does this seem right?

  25. Moving the script block out of the head tag and into the body fixed the issue, in case anyone else is experiencing it.

  26. @Chris Adams: There are problems adding script elements to body in IE. I would avoid doing that.

    @David Minor: You don’t want to move the snippet to the body – that will delay the script load resulting in less data.

    Can you clarify the problem you’re experiencing? Do you have a page without a HEAD element? The GA team is looking at some variant of document.getElementsByTagName(”head”)[0].appendChild that should fix that issue.

  27. @David Minor

    Replace:
    `document.documentElement.firstChild.appendChild(ga);`

    with:

    `var headEl = document.getElementsByTagName(‘head’)[0] || document.documentElement; headEl.insertBefore(ga, headEl.firstChild)`

  28. @Steve, sure — you get a popup as described in the MS knowledgebase article and an IE error page. The error doesn’t happen every time, but once it occurs it seems to happen more frequently.

    What’s strange is that we were already using the asynchronous loading technique for jquery and didn’t experience the problem.

    @kangax I’ll try that and report back.

  29. @kangax No luck — same problem

    @Steve The waterfall for having it in the head vs very beginning of the body is pretty similar in firebug. I’ve left it up with kangax’s suggestion if you want to try to get the error yourself (domain name same as my email). The easiest way to replicate I’ve found is to set it as the homepage, and repeatedly open IE.

  30. Just switched WebPagetest to async and it was pretty much a wash (but firing earlier so it will be more reliable).

    Before: http://www.webpagetest.org/result/091206_3E4P/

    After: http://www.webpagetest.org/result/091206_3E4Q/

    Now, if you could get them to do something about Adsense we’d be in much better shape. Looks like 15 of my 23 requests are for the single banner ad placement :(

  31. I know the method of async for ga.js right

    But still can not resolve who leave a page quickly won’t generate any analytics data

    Becaluse ga.js is large and download need time.

  32. Aha , i know . The new ga.js code provides _gaq array.

    And ga.js will get _gaq’s length and so on .

    So when the ga.js download it will analyze the _gaq .

    Use it to solve those who do not have time to download the ga.js code

    I am sorry my English is poor..

  33. Thanks everyone for your feedback on the new snippet. We have incorporated some of your suggestions and have updated our snippet recommendation. If your feedback was not incorporated, we may be still investigating it.

    http://code.google.com/apis/analytics/docs/tracking/asyncTracking.html

  34. Is there a typo in the google documentation?

    http://code.google.com/apis/analytics/docs/tracking/asyncMigrationExamples.html#EcommerceTracking

    In the section where it says, addItem

    _gaq.push(['_addItem',
    '1234', // order ID - required
    'DD44', // SKU/code
    'T-Shirt', // product name
    'Green Medium', // category or variation
    '11.99', // unit price - required
    '1' // quantity - required
    );

    They're missing the closing "]“

  35. Thanks for posting this, Steve.

    I would agree with Patrick (#30) that this didn’t really affect our render speed or time to Document Complete. It did, however, cause the tracking pixel to fire about 80 ms sooner on average, which I definitely consider a win.

  36. I am using a joomla site I want to use analytic…anybody help me to provide few words as shortly as possible for how to use analytic in my joomla site?

  37. Does anyone know of a Async version of the Google Website optimiser code? e.g…

    call section on the page
    function utmx_section() {}
    function utmx() {}(function() {
    var k = ‘WsoTestIdHERE123456′,
    d = document,
    l = d.location,
    c = d.cookie;
    function f(n) {
    if (c) {
    var i = c.indexOf(n + ‘=’);
    if (i > -1) {
    var j = c.indexOf(‘;’, i);
    return c.substring(i + n.
    length + 1, j < 0 ? c.length : j)
    }}}
    var x = f(‘__utmx’),
    xx = f(‘__utmxx’),
    h = l.hash;
    d.write(”)
    })();

    Thanks!

  38. Trying desperately to get GA asynch code to work without much joy.

    Tried the Google help forums without any response so far.

    Have reread the Google code site till I’m seeing double.

    At the moment no ecommerce sales are being correctly attributed; custom variables aren’t working and I’m not even sure if the code has been correctly implemented.

    Our programmers have spent quite a few hours on it already and they can’t figure it out.

    Would dearly love someone who actually knows how to properly implement this to have a look at the code and tell me where we’re going wrong.

    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', 'UA-7249617-2']);
    _gaq.push(['_setDomain', '.perthmint.com.au']);
    _gaq.push(['_trackPageview']);

    (function() {
    var ga = document.createElement(‘script’);
    ga.type = ‘text/javascript’;
    ga.async = true;
    ga.src = (‘https:’ == document.location.protocol ? ‘https://ssl’ : ‘http://www’) + ‘.google-analytics.com/ga.js’;
    (document.getElementsByTagName(‘head’)[0] || document.getElementsByTagName(‘body’)[0]).appendChild(ga);
    })();

    function TrackLink(newWindow,url,trackPath) {
    _gaq.push(['_trackPageview',trackPath]);
    if(newWindow==1)window.open(url,’newWindow1′)
    else location.href=url;
    }

  39. Hmm,

    your comment program seems to be stripping out the js tags.

    View the actuial code at http://www.perthmint.com.au

  40. @Mark: I think we got your problem fixed on the async help forum, right?

    http://www.google.com/support/forum/p/Google+Analytics/label?lid=5a6c689030bdafe7&hl=en

  41. Gosh, having trouble getting trustworthy eCommerce figures with the Async GA. Seems not to be working with multiple articles per order.

    Eyes are a bit fussy now.

    Maybe one of your brains still function properly?

    Code examples here:

    http://www.google.com/support/forum/p/Google+Analytics/thread?tid=7401ecce84077284&hl=en

    Getting a beer here.

    :)

  42. Still applicable with Google Caffeinated?

  43. Does anyone know if this works with wordpress? A plugin would be awesome.

  44. @ Web Designer Grant – It works on all websites. If you are using WordPress, add it to your site’s theme header or search online for ‘Google Analytics Plugin for WordPress’.
    You will find a lot that can help you use the code on your website.

    Richie.

  45. That’s a great improvement. Sometimes if the page takes too long to load, visitors will simply LEAVE! Definitely don’t want this happening because of the analytics code.

  46. Uptime is good, but I honestly can’t wait for Analytics to be a *bit more real-time data… think its currently off by about 3 hours and its nerve-wracking when you have a campaign pointing to your site and you’re wanting to see the results and user actions :-) Ah well, hopefully in the soon-to-be-future I suppose!

  47. I dont think analytic code has any effect on website speed.Is it?

  48. I agree – real time stats would be really useful.

  49. Real time stats would help in such situation!!!

  50. at the header,

    var _gaq = _gaq || [];
    _gaq.push(['_setAccount', 'UA-XXXXXXXX-X']);
    _gaq.push(['_trackPageview']);

    (function() {
    var ga = document.createElement(‘script’); ga.type = ‘text/javascript’; ga.async = true;
    ga.src = (‘https:’ == document.location.protocol ? ‘https://ssl’ : ‘http://www’) + ‘.google-analytics.com/ga.js’;
    var s = document.getElementsByTagName(‘script’)[0]; s.parentNode.insertBefore(ga, s);
    })();

    at the bottom of the page,
    _gaq.push(['_addTrans','1536','','669.00','','','','','']);_gaq.push(['_addItem','1536','8808992753520','42 LE 5300','LED','669.00','2']);_gaq.push(['_trackTrans']);

  51. But the above posted code does not track the e commerce transaction.!

  52. IE does not support ‘async’, so the analytic script file will block it from parsing and rendering the page?

  53. Real time data is now available on Google Analytics.

    @Salam: Setting ga.async=true is an extra optimization for browsers that support it. The snippet is still asynchronous even if the browser does not support the async property.

  54. > The snippet is still asynchronous even if the browser does not support the async property.

    is that because any script added dynamically (to the DOM by javascript) does not block browser’s page parsing?

  55. It’s only fair that Google is improving the speed of their tools when now integrating page-speed as a significant factor affecting page rankings. Way to go.