Track outbound links with Google Analytics (gtag.js)

Jul 12, 2020 JavaScript
Please note that this is not currently compatible with Google Analytics 4 (GA4).

This script will track outgoing links in Google Analytics using the gtag.js method (ie: global site tag). This should not be confused with Google Tag Manager. This is based on my previous script for Google Universal Analytics.js, but with some necessary changes to account for the newer gtag.js.

There is an official tutorial that also shows you how to do this, but that method is very flawed as it requires you to manually update all outbound link code on your website, doesn’t account for target properties, and will break if Google Analytics is blocked (ad blocking). The solution I wrote below integrates seamlessly, requires no change to your own HTML code (other than to include the JavaScript), and doesn’t break if Google Analytics is blocked or fails to load.

The best method of “auto-tracking” outgoing links is to automatically detect outbound links with JavaScript when they are clicked, and automatically track it as an event. The method below uses a “callback” function that, once the click has been registered with Analytics, the link is opened. This prevents the situation whereby the browser starts opening the new page before Analytics has had a chance to register the click. This method is by far the most robust, and simply means you need to include an external JavaScript file on your pages.

Features

  • Tracks all outgoing links as events in Google Analytics, including outbound link & referring page
  • Uses callbacks to first track and then redirect when outbound link opens in current window
  • Detects and works with Ctrl/Shift/Meta & middle-mouse clicks
  • Works even if Google Analytics is blocked

Tracking script

<script>
    function _gaLt(event) {

        // if GA is blocked or not loaded, or not main|middle|touch click then don't track
        if (typeof ga == 'undefined' || (event.which != 1 && event.which != 2)) {
            return;
        }

        var el = event.srcElement || event.target;

        // loop up the DOM tree through parent elements if clicked element is not a link (eg: an image inside a link)
        while (el && (typeof el.tagName == 'undefined' || el.tagName.toLowerCase() != 'a' || !el.href)) {
            el = el.parentNode;
        }

        // if a link with valid href has been clicked
        if (el && el.href) {
            var link = el.href;

            // only if it is an external link
            if (link.indexOf(location.host) == -1 && !link.match(/^javascript\:/i)) {

                // is target set and _(self|parent|top)
                var target = (el.target && !el.target.match(/^_(self|parent|top)$/i)) ? el.target : false;

                // assume a target if (ctrl|shift|meta)-click
                if (event.ctrlKey || event.shiftKey || event.metaKey || event.which == 2) {
                    target = "_blank";
                }

                var hbrun = false; // tracker has not yet run

                // hitBack to open link in same window after tracker
                var hitBack = function () {
                    // run once only
                    if (hbrun) return;
                    hbrun = true;
                    window.location.href = link;
                };

                if (target) { 
                    // if target opens a new window then just track
                    gtag('event', link, {
                        'event_category': 'Outgoing Links',
                        'event_label': document.location.pathname + document.location.search
                    });
                } else { 
                    // Prevent standard click, track then open
                    if (event.type == 'mousedown') {
                        var blockClick = function (event) {
                            event.preventDefault();
                            // remove click event after click
                            el.removeEventListener('click', boundClick);
                        }
                        // bind the click event
                        var boundClick = blockClick.bind(event)
                        // prevent the click
                        el.addEventListener('click', boundClick);
                    }
                    event.preventDefault ? event.preventDefault() : event.returnValue = !1;

                    // send event with callback
                    gtag('event', link, {
                        'event_category': 'Outgoing Links',
                        'event_label': document.location.pathname + document.location.search,
                        'event_callback': hitBack
                    });

                    // Run hitBack again if GA takes longer than 1 second
                    setTimeout(hitBack, 1000);
                }
            }
        }
    }

    var _w = window;

    /* Use "click" if touchscreen device, else "mousedown" */
    var _gaLtEvt = ("ontouchstart" in _w) ? "click" : "mousedown";
    /* Attach the event to all clicks in the document after page has loaded */
    _w.addEventListener("load", function () { document.body.addEventListener(_gaLtEvt, _gaLt, !1) }, !1);
</script>

Comments