MeasureCamp Split 2026 - Prezentacija

GTM za SPA
+ Consent

"Control Freak" pristup za vanilla JS SPA sa prilagođenim ruterom - modularan setup koji juniori mogu da debaguju bez straha.

Marko · Funky EnterprisesMeasureCamp Split · 2026GTM · GA4 · Consent Mode · SPA

> Problem

Standardni GTM setapi ne funkcionišu na Single Page Applications sa prilagođenim ruterima. History Change okidač se aktivira pre nego što DOM završi ažuriranje. Nativni GTM Consent Mode je crna kutija u SPA okruženjima. Rezultat: dupli eventi, nedostajući page_view-ovi i signali pristanka koji stižu prekasno.

Umesto jednog ogromnog nečitljivog skripta, ova prezentacija pokazuje modularan sistem - 6 tagova, 3 okidača, svaki sa tačno jednim zadatkom.

> 1. Arhitektura: 6 tagova, 3 okidača

01
Consent Bridge
// presreće ažuriranja pristanka (prioritet 999)
02
SPA Navigation Detector
// MutationObserver + History API
03
GA4 Loader
// učitava gtag, send_page_view: false
04
GA4 page_view
// aktivira se SAMO na spa_navigation
05
Meta Pixel
// uz poštovanje pristanka
06
Microsoft Clarity
// uz poštovanje pristanka
Okidač 1
Inicijalizacija pristanka - za Bridge i Detector
Okidač 2
gtm_consent_update - za Loader, Pixel, Clarity
Okidač 3
spa_navigation - za GA4 page_view
Argument: Svaka komponenta ima tačno jedan zadatak. Juniori mogu da debaguju sistem bez straha da će nešto pokvariti, jer je jasno koji tag šta radi.

> 2. Consent Bridge (Tag 52)

Prioritet 999. Zašto tako visok?

GTM ne "vidi" nativne pozive gtag('consent', 'update') u realnom vremenu unutar SPA. Bridge presreće te pozive i konvertuje ih u prilagođeni GTM event.

Custom HTML Tag // Prioritet 999
var origPush = window.dataLayer.push.bind(window.dataLayer);
window.dataLayer.push = function() {
  for (var i = 0; i < arguments.length; i++) {
    var item = arguments[i];
    if (item && typeof item === 'object' &&
        item[0] === 'consent' && item[1] === 'update') {
      var consentData = item[2] || {};
      origPush({
        'event': 'gtm_consent_update',
        'consent_analytics': consentData.analytics_storage || 'denied',
        'consent_ads': consentData.ad_storage || 'denied'
      });
    }
  }
  return origPush.apply(this, arguments);
};
Argument: Bridge presreće ažuriranje pristanka i konvertuje ga u prilagođeni event gtm_consent_update. Bez ovoga, GTM ne bi znao da se pristanak promenio u realnom vremenu unutar SPA.
Zašto: Ovo je jedini način da se garantuje da GA4 učita isti sekund kada korisnik klikne "Prihvati", umesto da čeka promenu stranice koja se nikad ne dešava u SPA.

> 3. SPA Navigation Detector (Tag 56)

MutationObserver + debounce od 150ms

Standardni "History Change" okidač se aktivira pre nego što prilagođeni ruter završi ažuriranje DOM-a.

Custom HTML Tag // aktivira se na svim stranicama
var lastPath = location.pathname;
function checkAndPush() {
  var currentPath = location.pathname;
  if (currentPath !== lastPath) {
    lastPath = currentPath;
    window.dataLayer.push({
      event: 'spa_navigation',
      page_location: location.href,
      page_path: currentPath,
      page_title: document.title,
      spa_timestamp: new Date().toISOString()
    });
  }
}
// MutationObserver sa debounce od 150ms
var observer = new MutationObserver(function() {
  clearTimeout(window._ferticoSpaTimeout);
  window._ferticoSpaTimeout = setTimeout(checkAndPush, 150);
});
observer.observe(document.documentElement, {
  childList: true, subtree: true
});
Argument: Koristimo MutationObserver koji prati promene DOM-a, kombinovan sa debounce od 150ms. Ovo je pouzdanije od čistog History API.
Zašto: Debounce od 150ms daje prilagođenom ruteru i Bootstrap 5 UI dovoljno vremena da završe renderovanje, tako da su page_location i page_title uvek 100% tačni.

> 4. Eliminacija duplikata (Tag 69 i 70)

send_page_view: false + ekskluzivni okidač

GA4 Loader (Tag 70) // Prioritet 100
if (window._ferticoGA4Loaded) return;
window._ferticoGA4Loaded = true;
var s = document.createElement('script');
s.async = true;
s.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
document.head.appendChild(s);
window.gtag('js', new Date());
window.gtag('config', 'G-XXXXXXXXXX', {
  'send_page_view': false,
  'transport_type': 'beacon'
});
Argument: GA4 page_view (Tag 69) se šalje isključivo kroz naš spa_navigation okidač. Nema više automatskih page_view eventa koje GTM generiše, a mi ne kontrolišemo.
Zašto: Potpuna eliminacija duplikata. Jedan izvor istine: naš spa_navigation event.

> Tok izvršavanja

[1]
Okidač za inicijalizaciju pristanka
// Consent Bridge (prioritet 999) se montira
// SPA Navigation Detector počinje da sluša DOM
[2]
Korisnik klikne "Prihvati" na cookie baneru
// Baner šalje: gtag('consent', 'update', {...})
[3]
Consent Bridge presreće i šalje
// event: 'gtm_consent_update'
[4]
Okidač gtm_consent_update se aktivira
// GA4 Loader (prioritet 100) učitava gtag.js
// Meta Pixel se učitava
// Microsoft Clarity se učitava
[5]
Korisnik navigira kroz SPA
// script.js menja DOM, MutationObserver detektuje
// Nakon debounce od 150ms: spa_navigation event
[6]
Okidač spa_navigation se aktivira
// GA4 page_view sa tačnim page_title i page_path

> Kontrolna lista pre puštanja u produkciju

[ ]Preview mode na 3 različite rute
[ ]Tag Assistant: 0 crvenih tagova
[ ]Pristanak: test scenarija "Odbij sve"
[ ]Test dugmeta Back u SPA
[ ]Dupli eventi: mora biti 0
[ ]Veličina kontejnera ispod 100KB
[ ]Bez hardkodovanih ID-jeva u imenima tagova
[ ]Naziv verzije: GGGG-MM-DD + kratak opis

> Ključni zaključci

>SPA ne aktivira page_view automatski. Morate preuzeti kontrolu.
>Consent bridge je privremeno rešenje, ali funkcioniše dok GTM ne dobije bolju SPA podršku.
>send_page_view: false + prilagođeni okidač = jedan izvor istine.
>MutationObserver + debounce je pouzdaniji od čistog History API u prilagođenom ruteru.
>6 čistih tagova sa imenima je bolje od 30 copy-paste HTML tagova.

Trebate čist GTM setup
za Vaš SPA?

Projektujemo modularne, održive analitičke arhitekture - i držimo vendore odgovornim za čistu implementaciju.