Question

I'm trying to accomplish tracking an event when a user leaves the page with Google Analytics (analytics.js). Though it is unknown how the user will leave, it may be because of an external link or just closing the tab. So my thought was to hook onto the beforeunload or unload event and then:

window.addEventListener("beforeunload", function() {
    ga('send', 'event', 'some', 'other', 'data');
});

Now my question is, will the request to the GA server be synchronous or can I somehow force that behaviour with the hitCallback property? If that is not possible, how else can I achieve this? Preferably without having to set a timeout or fixed waiting time for the user!

Was it helpful?

Solution 2

The request will not be synchronous, GA tracking calls never are.

The only way to ensure the call completes is to make sure the page stays open long enough - for an event on a link you would normally do this with a timeout potentially combined with a hitCallback, as you mentioned.

The only way to keep a window open when the user closes a tab is to return a value from your beforeunload handler, which will prompt a "Confirm Navigation" alert. That would be a really bad solution just to track a GA event, obviously.

OTHER TIPS

There is a way to make sure the request will be sent to GA. Simo Ahava wrote a very good blog post titled -
"Leverage useBeacon And beforeunload In Google Analytics".

Utilizing the brilliant sendBeacon solution. Here's quote which addresses the selected answer of this question:

User agents will typically ignore asynchronous XMLHttpRequests made in an unload handler. To solve this problem, analytics and diagnostics code will typically make a synchronous XMLHttpRequest in an unload or beforeunload handler to submit the data. The synchronous XMLHttpRequest forces the User Agent to delay unloading the document, and makes the next navigation appear to be slower. There is nothing the next page can do to avoid this perception of poor page load performance.

There are other techniques used to ensure that data is submitted. One such technique is to delay the unload in order to submit data by creating an Image element and setting its src attribute within the unload handler. As most user agents will delay the unload to complete the pending image load, data can be submitted during the unload. Another technique is to create a no-op loop for several seconds within the unload handler to delay the unload and submit data to a server.

Not only do these techniques represent poor coding patterns, some of them are unreliable and also result in the perception of poor page load performance for the next navigation.

Set transport to beacon, with ga:

window.addEventListener("beforeunload", function() {
    ga('send', 'event', 'page_unload', 'bye bye', {transport: 'beacon'});
});

Or transport_type to beacon, with gtag:

window.addEventListener("beforeunload", function() {
  gtag('event', 'page_unload', {
    'event_category': 'my cat',
    'event_label': 'my label',
    'transport_type': 'beacon'  // <--- important part here
  });
});

For what is worth, beacon should become the default transport mode anyway. As of 2020-09:

Currently, gtag.js only uses navigator.sendBeacon if the transport mechanism is set to 'beacon'. However, in the future, gtag.js will likely switch to using 'beacon' as the default mechanism in browsers that support it.

As pointed out by tomconnors, this does NOT work. I'm leaving the answer to help warn anyone thinking about doing it this way. Beacon transport is probably the way to go, but wasn't widely supported at the time of the original answer.

You can wait for a hit to be sent to Google Analytics in the page onunload, but it does require a busy loop. In my case this did not delay user navigation, as the page was a popup window that is dedicated to a webapp. I'd be more concerned about doing this inline with normal web page navigation. Still, I had to take 2 showers to get the filth off after committing code with a busy loop.

var MAX_WAIT_MS = 1000;
var _waitForFinalHit = false;

function recordFinalHit() {
    _waitForFinalHit = true;

    ga('send', 'event', 'some', 'other', 'data', {
        'hitCallback': function() {
            _waitForFinalHit = false;
        }
    });
}

function waitForFinalHit() {
    var waitStart = new Date().getTime();
    while (_waitForFinalHit
        && (new Date().getTime() - waitStart < MAX_WAIT_MS)) { }
}

function myOnUnload() {
    recordFinalHit();
    // Do your other final stuff here...
    waitForFinalHit();
}

window.onunload = myOnUnload;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top