Skip to content

Commit 28a486a

Browse files
committed
Google Analytics - performance optimization
1 parent 959fa8a commit 28a486a

File tree

2 files changed

+94
-12
lines changed

2 files changed

+94
-12
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Controller } from '@hotwired/stimulus';
2+
3+
/**
4+
* Stimulus controller for delayed Google Analytics loading with bot detection.
5+
*
6+
* Loads GA after user interaction (scroll, click, touch) OR a short timeout (300ms),
7+
* whichever comes first. Skips loading entirely for detected bots.
8+
*/
9+
export default class extends Controller {
10+
static values = {
11+
trackingId: String
12+
}
13+
14+
connect() {
15+
// Skip if no tracking ID or already loaded
16+
if (!this.trackingIdValue || window.gaLoaded) return;
17+
18+
// Bot detection first
19+
if (this.isLikelyBot()) {
20+
return;
21+
}
22+
23+
// Hybrid trigger: user interaction OR timeout (whichever first)
24+
this.setupTriggers();
25+
}
26+
27+
disconnect() {
28+
// Cleanup timeout if controller disconnects before firing
29+
if (this.timeout) {
30+
clearTimeout(this.timeout);
31+
}
32+
}
33+
34+
isLikelyBot() {
35+
// 1. WebDriver detection (headless browsers like Puppeteer, Playwright, Selenium)
36+
if (navigator.webdriver) return true;
37+
38+
// 2. Common bot user agents
39+
const ua = navigator.userAgent;
40+
const botPatterns = /bot|crawl|spider|slurp|facebookexternalhit|Twitterbot|WhatsApp|TelegramBot|preview|Lighthouse|PageSpeed|GTmetrix|Pingdom|Chrome-Lighthouse/i;
41+
if (botPatterns.test(ua)) return true;
42+
43+
// 3. Missing browser features real users have
44+
if (!navigator.cookieEnabled) return true;
45+
46+
try {
47+
if (!window.localStorage) return true;
48+
} catch (e) {
49+
// localStorage access can throw in private browsing
50+
return true;
51+
}
52+
53+
return false;
54+
}
55+
56+
setupTriggers() {
57+
const events = ['scroll', 'mousemove', 'touchstart', 'keydown', 'click'];
58+
59+
this.loadGA = () => {
60+
if (window.gaLoaded) return;
61+
window.gaLoaded = true;
62+
63+
// Cleanup listeners
64+
events.forEach(e => document.removeEventListener(e, this.loadGA));
65+
clearTimeout(this.timeout);
66+
67+
this.injectGA();
68+
};
69+
70+
// User interaction triggers
71+
events.forEach(e => document.addEventListener(e, this.loadGA, { once: true, passive: true }));
72+
73+
// Fallback timeout (300ms) - catches users who just read without interaction
74+
this.timeout = setTimeout(this.loadGA, 300);
75+
}
76+
77+
injectGA() {
78+
// Create and inject gtag.js script
79+
const script = document.createElement('script');
80+
script.src = `https://www.googletagmanager.com/gtag/js?id=${this.trackingIdValue}`;
81+
script.async = true;
82+
document.head.appendChild(script);
83+
84+
// Initialize gtag
85+
window.dataLayer = window.dataLayer || [];
86+
function gtag(){dataLayer.push(arguments);}
87+
window.gtag = gtag;
88+
gtag('js', new Date());
89+
gtag('config', this.trackingIdValue);
90+
}
91+
}

templates/base.html.twig

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@
121121
</script>
122122
{% endblock %}
123123
</head>
124-
<body class="lang-{{ app.request.locale }}" data-controller="gallery file-drop-area datepicker global-search toast">
124+
<body class="lang-{{ app.request.locale }}"
125+
data-controller="gallery file-drop-area datepicker global-search toast{% if ga_tracking is not empty %} analytics{% endif %}"
126+
{% if ga_tracking is not empty %}data-analytics-tracking-id-value="{{ ga_tracking }}"{% endif %}>
125127
{% if is_web() %}
126128
<header class="fixed-top shadow-sm" data-fixed-element>
127129
<div class="topbar topbar-light">
@@ -590,17 +592,6 @@
590592
</div>
591593
</div>
592594

593-
{% if ga_tracking is not empty %}
594-
<script async src="https://www.googletagmanager.com/gtag/js?id={{ ga_tracking }}"></script>
595-
<script>
596-
window.dataLayer = window.dataLayer || [];
597-
function gtag(){dataLayer.push(arguments);}
598-
gtag('js', new Date());
599-
600-
gtag('config', '{{ ga_tracking }}');
601-
</script>
602-
{% endif %}
603-
604595
{% if false %}
605596
<div class="modal fade in" tabindex="-1" role="dialog" id="global-modal" data-controller="autoshow-modal">
606597
<div class="modal-dialog modal-dialog-centered" role="document">

0 commit comments

Comments
 (0)