reagiraj.ba je alat za samoprocjenu rizika od raka pluća, napravljen za EU Interreg IPA projekat LIFEGATE. Riječ je o kratkom upitniku: nekoliko koraka, ekran s rezultatom koji prikazuje nivo rizika i preporuku, i ništa više. Nakon pokretanja platforme tim je trebao znati koliko ljudi je započelo upitnik, dokle su stigli i koliko ih je završilo.
Cijeli projekat opisan je u studiji slučaja reagiraj.ba, a ovakvo podešavanje dio je mog rada na analitici i praćenju.
Zvuči kao standardno praćenje funnela. Ispostavilo se da je to dobra lekcija o tome zašto "samo dodaj to u Google Tag Manager" ne radi uvijek kod single-page aplikacije.
Postavka
Upitnik je React single-page aplikacija. Svi koraci žive u stanju komponente. Prelazak s koraka 1 na korak 2 ne mijenja URL, ne pokreće učitavanje stranice i ne dodaje zapis u historiju. Iz perspektive browsera, posjetilac je cijelo vrijeme na istoj stranici.
Time otpada uobičajeni pristup praćenja pregleda stranica ili promjena historije po koracima. Nema se šta pratiti.
Prvi pokušaj: Element Visibility triggeri
Google Tag Manager ima Element Visibility trigger koji se aktivira kada se odgovarajući element pojavi na ekranu. Za konačnu konverziju to je radilo dobro. Ekran s rezultatom ima prepoznatljiv sadržaj, pa sam mogao poslati event "completed" kada on postane vidljiv.
Najvažnija konverzija je, dakle, bila pokrivena i brzo je puštena u rad. Problem je bilo sve prije nje.
Problem: nema stabilnih selektora
Da bih pratio korake 2 i 3, trebao mi je pouzdan način da ih prepoznam u DOM-u. Takvog nije bilo.
- Opcije odgovora bili su
divelementi srole="radio", bez imena i id-a. - Koraci su koristili identične utility klase, pa je kontejner koraka 2 izgledao isto kao kontejner koraka 3.
- Ništa u markupu nije govorilo "ovo je korak 2".
Mogao sam napisati krhke selektore zasnovane na redoslijedu elemenata ili tekstualnom sadržaju, ali oni pucaju čim neko izmijeni pitanje ili promijeni raspored. Za projekat u kojem brojke idu u izvještaje za bolnicu i EU projekat, krhko praćenje je gore od nikakvog.
To je suštinski problem praćenja preko CSS selektora u modernom frontendu: DOM je implementacijski detalj. Nije zamišljen kao API za analitiku i mijenja se kad god se promijeni UI.
Pravo rješenje: slanje eventova iz React stanja
Aplikacija već tačno zna na kojem je koraku posjetilac. To znanje živi u React stanju. Pravo mjesto za slanje eventova za praćenje je, dakle, sama aplikacija, a ne selektor u GTM-u.
Kad god se korak promijeni, šaljem event form_step u dataLayer. Pojednostavljena verzija izgleda ovako:
// Pojednostavljena skica, nije produkcijski kod
import { useEffect, useRef } from "react";
declare global {
interface Window { dataLayer?: Record<string, unknown>[] }
}
export function useStepTracking(step: number, totalSteps: number) {
const lastTracked = useRef<number | null>(null);
useEffect(() => {
if (lastTracked.current === step) return; // bez duplikata pri ponovnom renderu
lastTracked.current = step;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: "form_step",
form_step: step,
form_total_steps: totalSteps,
});
}, [step, totalSteps]);
}
U GTM-u Custom Event trigger osluškuje form_step, a Data Layer Variable čita broj koraka. Odatle eventovi koraka idu u GA4 kao i svaki drugi event, a funnel izvještaj radi bez ijednog CSS selektora.
Isti hook mi daje nešto što visibility trigger nikad nije mogao: kontrolu nad time šta se računa kao konverzija. Visibility trigger se aktivira kad god se element pojavi, čak i ako slanje nije uspjelo, a stanje greške je prikazalo nešto slično. Kada event dolazi iz aplikacije, event o završetku mogu poslati tek nakon što je slanje zaista uspjelo. Neuspjela slanja se više ne računaju kao leadovi.
Opće pravilo koje sam iz ovoga izvukao: GTM je dobar sloj za usmjeravanje, ali aplikacija treba biti izvor istine o tome šta se desilo. Ako se u stanju aplikacije desi nešto smisleno, pošaljite event iz stanja.
Privatnost: pažljivo mjerenje zdravstvenog alata
Ovaj dio mi je bio važniji od tehničkog problema.
reagiraj.ba je zdravstveni alat. Ljudi unose podatke o pušenju, simptomima i historiji bolesti, a zauzvrat dobijaju nivo rizika. To su osjetljivi podaci. Mjerenje korištenja nikad ne smije značiti da bilo šta o nečijem zdravlju procuri u analitiku ili do bilo koje treće strane.
Zato sam povukao jasnu granicu oko onoga što napušta aplikaciju:
- Nivo rizika se nikad ne šalje u analitiku. GA4 zna da je neko završio upitnik. Ne zna da li je rezultat bio nizak, umjeren, visok ili hitan.
- Bez oglašivačkih profila: ništa o zdravlju osobe ne napušta aplikaciju.
Eventovi koraka slijede isto pravilo. Nose broj koraka i ništa drugo: bez odgovora, bez mjerenja, bez bodova.
Bolnički tim i dalje dobija analitiku koja mu treba, ali na pravom mjestu. Administratorski dashboard iza aplikacije, s vlastitom bazom podataka, prikazuje demografsku, geografsku i analitiku rizika za ljude koji vode projekat. Analitika uvijek vidi samo anonimne eventove funnela.
Šta bih sljedeći put uradio od početka
Kad bih ponovo gradio sličnu aplikaciju s više koraka, hook za praćenje bih dodao prvog dana, zajedno s kratkim planom praćenja: koji eventovi postoje, koje podatke svaki nosi i šta se nikad ne smije poslati. Tokom razvoja to oduzima vrlo malo vremena, a izbjegava se kasna jurnjava kada je aplikacija pred pokretanjem.
A za sve što je vezano za zdravlje, pravila privatnosti bih zapisao prije nego što napišem ijedan tag. Mnogo je lakše osmisliti mjerenje oko jasne granice nego naknadno provjeravati šta ju je možda prešlo.
