Google Analytics events when GA loads late
I wanted to know which links people actually use on this site. Do they email me, or open LinkedIn? On /mobifreaks, which templates do they open, and which do they download?
Google Analytics 4 was already on the site. It doesn’t load with the page. It waits for the first scroll, tap or key press, or 3.5 seconds after load, then downloads gtag.js. That keeps it out of the way of PageSpeed Insights. It is also skipped for anyone whose browser sends Do Not Track or Global Privacy Control.
A helper that is always there
The analytics component defines one function on every page:
window.__track = window.__track || function () {};
When GA is on, it replaces it:
window.__track = function (name, params) { gtag('event', name, params || {}); };
Events sent before gtag.js arrives wait in dataLayer, and GA sends them once it loads. When GA is off, __track does nothing, so the rest of the site can call it without checking.
The events
Email and social links share one listener in the layout. The social links already carry rel="me" for my profiles, so I used that to find them:
document.addEventListener('click', function (e) {
var a = e.target.closest && e.target.closest('a[href^="mailto:"], a[rel~="me"]');
if (!a) return;
var where = a.closest('footer') ? 'footer' : a.closest('#contact') ? 'contact' : 'page';
if (a.protocol === 'mailto:') window.__track('email_click', { location: where });
else window.__trackLink(e, a, 'social_click', { network: a.title || a.hostname, location: where });
});
That gives me:
email_click, andemail_copyfrom the copy button next to my addresssocial_click, withnetwork(LinkedIn, GitHub, Dribbble) and where the link was
On Mobifreaks:
mobifreaks_template_click, with the template and whether it was a colour swatch, a screenshot or a namemobifreaks_demowhen a demo opens in the drawermobifreaks_drawer_close, with how many seconds it was openmobifreaks_download, with the template and whether it came from the page or the drawer
The extra values like network and template only show in GA reports after you add them under Admin, Custom definitions. Event counts work without that.
The click that leaves first
Then I found a gap. Say someone opens the site and their very first action is tapping my LinkedIn icon. That tap is what starts loading gtag.js. The event goes into dataLayer, the browser leaves for LinkedIn, and the page is gone before GA ever loaded. The click is lost. Downloads had the same problem.
Once gtag.js has loaded, this doesn’t happen. GA sends events in a way that survives the page closing. So the fix only needs to cover that first moment.
That’s what __trackLink is for:
window.__trackLink = function (e, a, name, params) {
var leaves = !e.defaultPrevented && e.button === 0 && !(e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) &&
(!a.target || a.target === '_self') && !a.hasAttribute('download');
if (ready || !leaves) return window.__track(name, params);
e.preventDefault();
var gone = false;
function go() { if (!gone) { gone = true; location.href = a.href; } }
gtag('event', name, Object.assign({}, params, { event_callback: go, event_timeout: 1000 }));
setTimeout(go, 1000);
load();
};
readyturns true whengtag.jshas loaded. After that, nothing is held.leavesis true only for a plain click that replaces this page. Cmd or Ctrl clicks and new tabs are never held.- Otherwise it stops the click, sends the event, and follows the link when GA confirms it with
event_callback. - If GA never answers, for example because an ad blocker stopped it,
setTimeoutfollows the link after 1 second anyway.
Email links don’t need any of this, because mailto: doesn’t leave the page.
How long it holds
I tested it with a stand-in for gtag.js, so no test clicks reached my real GA property. On the local dev server:
- First click, GA loads: the link opened after 245 to 371ms.
- First click, GA blocked: about 1.1 seconds, the cap.
- Click after GA had loaded: 53 to 68ms, with nothing held.
The dev server is slower than the live site, so the first number is on the high side. The page load itself didn’t change at all.
This was the first half. Taps still felt slow on a phone, so I worked on that next in Prefetch on touch in an Astro site.