lyncd

Better Google Analytics JavaScript that doesn’t block page downloading

If you saw Steve Souders’ March 5 talk at Google and have ever used Google Analytics, then you were probably amused when he used GA’s JavaScript insertion code as his “wrong” counterexample. Well, at least I was, and somebody else mentioned it in the Q&A so I figure I’m not the only one.

The GA insertion code that Google gives you to put on your site does a couple of bad things: First, it uses document.write, and second, it loads ga.js directly, which blocks browsers from doing any page rendering or downloading of other page components (images, scripts, stylesheets) during the whole time it takes ga.js to download and execute. In other words, Google Analytics makes your pages load slower!

Using Steve’s best practices, I’ve coded up a better version that does DOM insertion of the script tag and uses the “script onload” technique to initialize the tracker, so that it doesn’t block I/O, and you can inline it anywhere on the page or even load it from an external file. You can choose to lazy-load GA whenever you want — for instance, even after window.onload fires — so that it’s totally asynchronous and doesn’t interfere with page rendering at all.

If you’ve got no idea who Steve Souders is (former front-end optimization guru at Yahoo, and now at Google) or what I’m talking about, I strongly recommend you check out Steve’s book, High Performance Web Sites, and its 14 rules for high-performance sites, which are also summarized on Steve’s site and Yahoo’s Exceptional Performance blog. Steve is currently working on a follow-up book with 10 (so far) new rules, which you can get a sneak peak at by watching this talk on the first three and the March 5 talk on the next three (skip to 39:00 to see him bring up GA). Steve also gave a talk at SXSW (slides) yesterday.

The code

Here’s the Google Analytics insertion code that I put together from Steve’s examples — it’s pretty simple, really. Instead of the code Google tells you to use, you can just copy-paste this instead. You can use it inline, or load it from an external .js file:

(Updated 3/27 per thread)

/*
Inserts GA using DOM insertion of <script> tag and "script onload" method to
initialize the pageTracker object. Prevents GA insertion from blocking I/O!

As suggested in Steve Souder's talk. See:

http://google-code-updates.blogspot.com/2009/03/steve-souders-lifes-too-short-write.html

*/

/* acct is GA account number, i.e. "UA-5555555-1" */
function gaSSDSLoad (acct) {
  var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."),
      pageTracker,
      s;
  s = document.createElement('script');
  s.src = gaJsHost + 'google-analytics.com/ga.js';
  s.type = 'text/javascript';
  s.onloadDone = false;
  function init () {
    pageTracker = _gat._getTracker(acct);
    pageTracker._trackPageview();
  }
  s.onload = function () {
    s.onloadDone = true;
    init();
  };
  s.onreadystatechange = function() {
    if (('loaded' === s.readyState || 'complete' === s.readyState) && !s.onloadDone) {
      s.onloadDone = true;
      init();
    }
  };
  document.getElementsByTagName('head')[0].appendChild(s);
}

/* and run it */
gaSSDSLoad("UA-5555555-1");

Be sure to use your own tracking code in place of “UA-5555555-1.”

Other things to consider

Using code like this means you could choose to load the GA tracker higher up in your pages, with less of a penalty versus Google’s insertion code, which causes serious blocking if it isn’t at the very bottom just before </body>. It’s still best to load GA last unless you really want to count every pageview possible.

You could also modify the last line of my example so that gaSSDSLoad() isn’t triggered until after window.onload (I didn’t do this in my example, because I wanted it to be a drop-in replacement for Google’s code). That way, GA doesn’t even start downloading or executing until after your page is displayed. For instance:

window.onload = function () { gaSSDSLoad("UA-5555555-1"); };

If you’re using Dojo, you might want to take a look at this Dojo module (mentioned by Steve in his talk), which also lazy-loads GA. My version is different in that (1) it’s standalone and doesn’t require Dojo and (2) it uses the “script onload” method to detect when ga.js has finished downloading (as Steve suggested), instead of polling with a timer. That means it does a slightly better job of firing _gat._getTracker() as soon as ga.js is loaded, compared to the Dojo version.

In his talk, Steve said he hoped the Dojo folks would incorporate his suggestions and improve their version. That’s all well and good, but considering where Steve works, let’s hope Google takes his suggestions! (Otherwise, the irony of “Google is the new Yahoo” will be just too much.)

I’ve been running my version for a few days and haven’t noticed any problems (no traffic drop-offs from any browsers/user agents), but no guarantees of course. Please let me know if you notice any bugs or have suggestions! (First person to tell me they already wrote this exact thing 6 months ago gets a special prize. :) )

Filed under: Code.  Tagged: , , .

14 comments »

  • Good, solid article, txs.

    I watched Steve’s talk was also suprised that he took GA as an example of “bad”.
    Steve is great and I can’t wait to get my hands on his second
    Keep up the good work on your blog … and please stick to this or another minimalist design: simple + clean = good.

  • I had to change the line:
    ‘loaded’ === s.readyState
    to
    ‘loaded’ === s.readyState || ‘complete’ === s.readyState
    because IE7 have the readyState = “complete” when the script is cached.
    Also, Opera doesn’t seem to work properly, when the script is cached, the init() function is called but the code doesn’t seem to run… A quick workaround I made was to add a query string parameter with a random value every time. The other browsers I tested doesn’t have any problem (Chrome, FF3 and Safari).

    • Thanks very much for letting me know! I’ve changed my example code to check for || ‘complete’ === s.readyState. I think somewhere in the dark recesses of my mind I already knew this about IE, but sure didn’t remember it when I was adapting the example code!

      With respect to Opera, I haven’t been able to produce any problems (Opera 10 alpha, both with and without new Delayed Script Execution setting enabled). But then, I’ve been running/testing slightly different code (a couple of functions abstracted and the whole thing abstracted into a module). So, I’ve also modified my example code above to better match my abstracted version in case that eliminates the problem you’re having.

      I wonder if you retest if you’ll still have problems with Opera when ga.js is loaded from cache? If so, can you tell me what version of Opera you’re testing so I can debug? You might also use the Opera Developer Tools console on this website … you can check in the network tab to see that __utm.gif is loaded, and use the scripts tab / command line to check the value of lyncd.log, it’s an array that’ll include the string “GA executed” if the callback that initializes the GA pagetracker has been run.

      Thanks again for the bug report and solution!

      • Nice idea, nice script and thanks for publishing it!

        As Bruno mentioned, it fails in Opera when the script is cached. The problem isn’t present with the latest Opera 10 weeklies, though in the last few release versions (9.6++) it fails on “_gat”. Had this exact problem when creating a Twitter widget a while back, at the time the daily snapshot of Google Chrome exhibited this problem as well (haven’t re-tested). With my widget, the no-cache workaround solved the issues in both browsers.

        Here’s a screenshot of Opera’s error console, viewing this post with Opera 9.64 (the latest release).

        I’ve implemented what I hope is a workaround at my sandbox (js). I’m fairly new to Google Analytics and I’m no JavaScript wizard, so I have no idea if it actually solves the problem or introduces new issues (though no errors are reported!).

  • Even using this script to load Google Analytics, I still occasionally have my web page HTML blocked in rendering while my Firefox 3.0 status bar says

    “Transferring data from http://www.google-analytics.com...”

    Any ideas?

  • Nice code, clean, and fast.
    One note to all those people who are working on the operafix. Adding a random parameter to the ga.js file so it fixes the cache problem defeats the use of this script all together and it will add download time to all your pages.

  • Using Safari “Web Inspector”, it becomes clear to see that this still blocks rendering.

    Any suggestions on a work around?

  • I am confused, what is the “, pageTracker, s” part of the line that defines the var gaJsHost?

  • Very useful, thanks for posting.

  • Thank you so much for this! I’m not a programmer at all, but this was really easy to follow, my pages are loading faster and I’ve “checked status” a few times and everything seems to be running fine.

  • [...] Google Analytics also decided to pose a problem it wanted to block all incoming/outgoing/pretty much any request. Which is bad. So I moved the code around and started using a small script from here: http://lyncd.com/2009/03/better-google-analytics-javascript/ [...]

  • Hi mate, thanks for that function. Worked really well!

Add a comment

You can also log in (or register) for easier commenting on lyncd.