Speeding up Google Analytics load times with a jQuery plugin
Update 2009/09/26: Thanks to Geekology reader Armin for pointing out that the jquery.geekGa-1.1.js plugin doesn’t have the ability to track views and events on subdomains. This functionality has been integrated in the latest version of the plugin, read more about it here.
Update 2009/08/10: Thanks to the Geekology readers who pointed out that the previous version of the geekGaTrackPage function reloaded script files without caching them; This has now been corrected by using a jQuery AJAX call with caching enabled. The new code is displayed below, and the new version of the plugin can be downloaded at the bottom of the post.
Google Analytics is a great web analytics tool to track Visitor, Traffic Source and Content statistics for free.
Implementing Google Analytics on a website requires that you create an account at http://www.google.com/analytics and add the Analytics Tracking Code to the website body:
<!-- Google Analytics --> <script type="text/javascript"> var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); </script> <script type="text/javascript"> try { var pageTracker = _gat._getTracker("UA-0000000-0"); pageTracker._trackPageview(); } catch(err) {} </script> <!-- Google Analytics -->
However, there are several problems with this code:
- It requires JavaScript code to be written within the HTML content, which is usually considered bad form.
- The ‘document.write‘ statement executes where it’s encountered, it cannot inject code at a given node point.
- The ‘document.write‘ statement effectively writes serialised text, which is not the way the DOM works conceptually and is an easy way to create bugs.
- The ‘document.write‘ statement breaks pages using XML rendering (e.g. XHTML pages)
- It loads ga.js directly, blocking browsers from continuing page rendering or content downloading (such as scripts, stylesheets or images) for as long as it takes ga.js to download and execute.
To keep the Google Analytics code from interfering with page rendering you can use jQuery to load and execute the ga.js file.
The ‘jquery.geekga.js’ jQuery Plugin:
Geekology.co.za makes use of a custom jQuery plugin to load ga.js and track pageviews & events:
/* * jquery.geekga.js - jQuery plugin for Google Analytics * * Version 1.1 * * This plugin extends jQuery with two new functions: * * - $.geekGaTrackPage(account_id) * Track a pageview. * * - $.geekGaTrackEvent(category, action, label, value) * Track an event with a category, action, label and value. * * * This code is in the public domain. * * Willem van Zyl * willem@geekology.co.za * http://www.geekology.co.za/blog/ */ (function($) { var pageTracker; /** * Track a pageview, e.g.: * * $.geekGaTrackPage('UA-0000000-0'); */ $.geekGaTrackPage = function(account_id) { //check whether to use an unsecured or a ssl connection: var host = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); var src = host + 'google-analytics.com/ga.js'; //load the Google Analytics javascript file: $.ajax( { type: 'GET', url: src, success: function() { //the ga.js file was loaded successfully, set the account id: pageTracker = _gat._getTracker(account_id); //track the pageview: pageTracker._trackPageview(); }, error: function() { //the ga.js file wasn't loaded successfully: throw "Unable to load ga.js; _gat has not been defined."; }, dataType: 'script', cache: true } ); //old method, doesn't cache the script file: /* $.getScript(src, function() { if (typeof _gat != undefined) { //the ga.js file was loaded successfully, set the account id: pageTracker = _gat._getTracker(account_id); //track the pageview: pageTracker._trackPageview(); } else { //the ga.js file wasn't loaded successfully: throw "Unable to load ga.js; _gat has not been defined."; } }); */ }; /** * Track an event, e.g.: * * $('a.twitter').click(function() { * $.geekGaTrackEvent('feed', 'click', 'Twitter', 'willemvzyl'); * }); */ $.geekGaTrackEvent = function(category, action, label, value) { if (typeof pageTracker != undefined) { //the pageTracker was defined, track the event: pageTracker._trackEvent(category, action, label, value); } else { //the pageTracker wasn't defined: throw "Unable to track event; pageTracker has not been defined"; } }; })(jQuery);
Using the ‘jquery.geekga.js’ jQuery Plugin:
To use the plugin, include it and jQuery in the website’s head:
<html> <head> <title>Hello, world!</title> <script src="javascript/jquery-1.3.2.min.js" type="text/javascript"></script> <script src="javascript/jquery.geekga-1.1.min.js" type="text/javascript"></script> </head> <body> <p>Hello, world!</p> </body> </html>
… then track pageviews or events using the ‘geekGaTrackPage‘ or ‘geekGaTrackEvent‘ functions.
The geekGaTrackPage function requires a single parameter: the ID of the associated Google Analytics account. This ID is the value starting with “UA-” in the
var pageTracker = _gat._getTracker('UA-0000000-0');
…line of the default Analytics Tracking Code.
The geekGaTrackEvent function requires four variables: Category, Action, Label and Value, as defined in the Google Analytics API’s Event Tracking Overview.
To call these functions, you can embed some jQuery code in the HTML code:
<html>
<head>
<title>Hello, world!</title>
<script src="javascript/jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="javascript/jquery.geekga-1.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
$.geekGaTrackPage('UA-0000000-0');
});
</script>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>… but since part of the idea behind this plugin was to remove the need for embedded JavaScript, it’s best to call these functions from a separate JavaScript file and only after the page has finished loading when jQuery’s ‘$(document).ready()‘ function executes:
$(document).ready(function() { $.geekGaTrackPage('UA-0000000-0'); $("a[href='http://www.geekology.co.za/blog/feed/']").each(function() { $(this).click(function() { $.geekGaTrackEvent('feed', 'click', 'RSS 2.0', 'articles'); }); }); $("a[href='http://www.twitter.com/willemvzyl/']").each(function() { $(this).click(function() { $.geekGaTrackEvent('feed', 'click', 'Twitter', 'willemvzyl'); }); }); $("a[href='http://www.geekology.co.za/blog/']").each(function() { $(this).click(function() { $.geekGaTrackEvent('page', 'click', 'Home', ''); }); }); });
The jquery.geekga.js plugin version 1.1 can be downloaded here, or downloaded in a minified form here.
Related posts:
- Subdomain tracking update to the geekGa.js jQuery plugin for Google Analytics
- Minify JavaScript code to obfuscate details and decrease load times
- 12 Tips to improve your jQuery code
- Disabling auto-formatting of telephone numbers by the Skype Browser Plugin
- Google Chrome Frame changes Internet Explorer into Google Chrome!



05 Aug 2009 








author
Wow Willem, now that’s a nifty piece of code! I will definately be using this in future!! Thanks mate!
Hey George
Sure, I’m glad you found it useful! My next step is to add addition API integration such as Time Tracking, Histograms, etc.
Thanks for sharing! If you drop the `fn` and just assign to `$` directly you don’t need the `$()` before every call, e.g.
$.geekGaTrackPage = function(account_id) { … }
then:
$.geekGaTrackPage(‘UA-0000000-0′);
Aaah, great, thanks Chris!
This is all heroic and great, but it if ga code is put to the end of the markup (right before ), as it’s laid out in ga document as preferred practice, most of the problems are miraculously gone. GA code won’t become nicer, but that’s just about it.
@tjp: I assume you meant to say right before the closing body tag?
Adding the GA code anywhere in the HTML causes several problems other than just page load speeds, as mentioned above:
-You’re embedding JavaScript (the behavioural layer) inside your HTML code (the content layer), whereas ideally your content, presentation and behavioural layers should be kept separate.
-The GA code uses document.write, a very inefficient way to add to a document body.
-While the ga.js file is downloading and executing, the browser won’t continue rendering the page (adding the closing body and HTML tags), so certain style issues can temporarily appear.
Additionally, to make use of custom click tracking, time tracking, and other GA API functionality (not just pageview tracking), the GA code needs to be inserted right after the opening body tag, the worst possible place it could be.
All of the above are issues that the jquery.geekja.js plugin try to circumvent.
@willem – first point is aesthetic, second and third points would be great backed up with actual data ;]. Fact, I had loading problems with GA code 2-3 years ago. Again, I appreciate you manage to break out from the sequential loading issue, but in practicality, it’s rarely a problem, at least with GA. As said, I’m not happy with the crap Analytics spits out, personally would prefer if it was included in the ajax loader api (which is essentially the same as your piece.)
“Additionally, to make use of custom click tracking, time tracking, and other GA API functionality (not just pageview tracking), the GA code needs to be inserted right after the opening body tag, the worst possible place it could be.”
There’s a factual error here – you need GA code _to be loaded_ in order to fire an event. Events most usually occur when the page is loaded (including ga), so it’s very rarely a problem. If you want to measure page load time, you probably start counting in the html head and send the ga event when $(window).load happens (and you have ga loaded).
@tjp: About my first point, “ideally your content, presentation and behavioural layers should be kept separate”:
In web design the idea of separation of content (HTML), presentation (CSS) and behaviour (JavaScript) has been around for years and is certainly not just a case of aesthetics. It makes practical sense for several reasons:
- Separation allows for the caching of external JS and CSS files so pages load faster
- Separation allows for cleaner, easier-to-read code
- Separation allows for graceful degradation where devices don’t support JS or CSS
- Separation and semantic markup improves accessibility by making content easier to navigate for screen reading devices
Two articles you can take a look at for further explanations are:
http://www.mercurytide.co.uk/news/article/separation-structure-presentation-and-behaviour/
http://www.alistapart.com/articles/behavioralseparation
Second point, “the GA code uses document.write, a very inefficient way to add to a document body”:
- document.write doesn’t work well with XML or XHTML documents (see the links below)
- document.write can only inject content at the point where it’s executed, meaning that it forces you to embed your JS in the document body, breaking the 3 layers of separation mentioned above.
Further reading:
http://ejohn.org/blog/xhtml-documentwrite-and-adsense/
http://www.w3.org/MarkUp/2004/xhtml-faq#docwrite
http://www.intertwingly.net/blog/2006/11/10/Thats-Not-Write
Third point, “while the ga.js file is downloading and executing, the browser won’t continue rendering the page”:
This is pretty well explained and demonstrated here:
http://www.schillmania.com/content/entries/2009/defer-script-loading/
http://www.stevesouders.com/blog/2009/04/27/loading-scripts-without-blocking/
http://stevesouders.com/hpws/js-blocking.php
http://stevesouders.com/cuzillion/?ex=10008&title=Scripts+Block+Downloads&t=1249634398
As for the factual error you mentioned, yes I agree that a better way to state it would be that “To make use of custom click tracking … and other GA API functionality (…), the GA code needs to be loaded before such events occur”.
Where I disagree, however, is the assumption that events will occur after the page has finished loading. Even click events (not just time-tracking, mouseover, etc. events) might happen before a page has finished loading, especially on slower connections.
Measuring page load time would work the way you proposed, but if you wanted to track how long incremental data took to load, mouseovers the user performed on certain elements, etc. (events that complete before the page has finished loading and a before-closing-body-tag GA implementation executes) to create time-based histograms, you’d need to load GA early-on. With the standard GA code this would require that you add the code right after the opening body tag.
See here for an example of time tracking and histograms with Google Analytics:
http://code.google.com/apis/analytics/docs/tracking/eventTrackerWrappers.html
See also my solution with script load cache: http://playground.ebiene.de/2148/google-analytics-mit-jquery/
Thank you.
Work for me. I measure by firebug that page load faster.
willem -
I loved the back & forth with tjp.
You got it right. Good links, thanks for the extra info.
I hope more people read comments
– joej
$(“a[href='http://www.geekology.co.za/blog/feed/']“).each(function() {
$(this).click(function() {
$().geekGaTrackEvent(‘feed’, ‘click’, ‘RSS 2.0′, ‘articles’);
});
});
can be better :
$(“a[href='http://www.geekology.co.za/blog/feed/']“).click(function() {
$().geekGaTrackEvent(‘feed’, ‘click’, ‘RSS 2.0′, ‘articles’);
});
the each is useless…
Hi Sylvain
The reason I used ‘each’ is that I can have several instances of a specific link on my pages and I want to apply the Click Tracker to all of them.
For example, the homepage of this blog contains two links to ‘http://www.geekology.co.za/blog/’: The website logo and the “Home” link below the search box.
Additionally, if I mention a link like my Twitter or Digg account in a post, I’d like that to be automatically tracked too.
Very nice work. Helped me very much, in dealing with Flowplayer events. Thanks!
Hi, I have found your script to be really, really useful for most cases.
However, today I needed to track subdomains as described here: http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55524
So I made few changes to script I use.
(1) It is now called with: $.geekGaTrackPage(‘UA-XXXXXXX-X’, ‘.domain.name’);
(2) In jquery.geekga.js:
(line 22) -> $.geekGaTrackPage=function(account_id, domain),
(line 46) -> “pageTracker._setDomainName(domain);”
Would be cool if you could include this as a separate release for this case.
Thanks, Armin!
I’ve integrated the functionality you suggested, you can read about it (and download the latest version of the plugin) here:
Subdomain tracking update to the geekGa.js jQuery plugin for Google Analytics
Hi Sylvain
The reason I used ‘each’ is that I can have several instances of a specific link on my pages and I want to apply the Click Tracker to all of them.
For example, the homepage of this blog contains two links to ‘http://www.geekology.co.za/blog/’: The website logo and the “Home” link below the search box.
Additionally, if I mention a link like my Twitter or Digg account in a post, I’d like that to be automatically tracked too.
Can we use this with Google analytics asenkron code?
Hi
The Google Analytics asynchronous tracker actually supercedes this plugin.