Shoper RWD: gdy funnel leci przez gtag zamiast dataLayer
Na Shoper Premium RWD funnel ecommerce potrafi lecieć przez gtag zamiast do dataLayer, a Custom Event triggery milczą. Jak to wykryć, checkbox który ożywia funnel i dummy ID na podwójne liczenie.
Jest taki objaw, który potrafi doprowadzić do szału. Piksel bazowy strzela, PageView strzela, kontener GTM ładuje się bez błędu, a mimo to ViewContent, AddToCart i InitiateCheckout milczą. Tag Assistant pokazuje zielono na bazie, ale lejek jest pusty. Sprawdzasz Custom Event triggery raz, drugi, trzeci - wyglądają poprawnie. A i tak nic.
Na Shoper Premium RWD winowajcą bywa coś, czego nie widać na pierwszy rzut oka: natywna wtyczka GA4 Shopera wysyła funnel ecommerce przez gtag('event', ...), a nie do dataLayer. Twoje triggery nasłuchują na dataLayer, więc dla nich tych zdarzeń po prostu nie ma. Strzela tylko to, co odpalasz na surowym page view, czyli piksel bazowy.
W tym artykule pokażę jak ten objaw rozpoznać w dwie minuty, dlaczego gtag('event') to nie to samo co dataLayer.push, jeden checkbox w panelu Shopera który ożywia cały funnel, oraz dlaczego po jego włączeniu dostaniesz podwójne liczenie w GA4 i jak je zdjąć techniką dummy measurement ID. Na koniec dorzucę adapter shopLayer do dataLayer z normalizacją prefiksowanego item_id i pułapkę pola Integracje własne, która łamie wklejony kod w środku tokena.
To operacyjny brat artykułu o tym, czym różnią się warstwy danych shopLayer i dataLayer na Shoper Storefront. Tamten wyjaśnia koncept warstw, ten pokazuje konkretną pułapkę na RWD i jak ją odkręcić.
Objaw: tylko piksel bazowy żyje, reszta funnela cisza
Konfiguracja wygląda tak: masz GTM na sklepie, tag bazowy Meta na page view, plus cztery tagi funnel (ViewContent, AddToCart, InitiateCheckout, Purchase) z Custom Event triggerami. Każdy trigger nasłuchuje na konkretny event w dataLayer, na przykład view_item, add_to_cart, begin_checkout.
Wchodzisz w GTM Preview Mode, robisz przejście klient -> karta produktu -> koszyk -> checkout. I widzisz, że:
- page view i tag bazowy odpalają się prawidłowo,
- żaden z czterech Custom Event triggerów funnel nie odpala się ani razu,
- w zakładce Data Layer Preview nie ma obiektu
ecommerceprzy żadnym kroku.
Pierwszy odruch to grzebać w triggerach. To błędne tropy. Triggery są poprawne, tylko nie mają na co reagować, bo zdarzenie, którego oczekują, nigdy nie trafia do dataLayer.
W praktyce u jednego sklepu modowego na Shoper Premium RWD dokładnie ten obraz utrzymywał się mimo poprawnej konfiguracji GTM. Cały lejek Meta był martwy, działał wyłącznie piksel bazowy. Naprawa nie polegała na zmianie ani jednego triggera.
Dlaczego gtag(‘event’) to nie dataLayer.push
dataLayer to globalna kolejka, do której każdy wpychany obiekt jest widoczny dla wszystkich tagów GTM jako trigger lub źródło zmiennych. Tak wygląda zdarzenie funnel w standardzie GTM:
window.dataLayer.push({
event: 'add_to_cart',
ecommerce: {
currency: 'PLN',
value: 99.99,
items: [
{
item_id: 'SKU_001',
item_name: 'Sukienka midi',
price: 99.99,
quantity: 1
}
]
}
});
Custom Event trigger w GTM dosłownie nasłuchuje na pole event w dataLayer. Gdy zobaczy add_to_cart, odpala przypięte tagi.
Natywna wtyczka GA4 Shopera na RWD często wysyła to samo zdarzenie zupełnie inną drogą, prosto przez globalną funkcję gtag:
gtag('event', 'add_to_cart', {
currency: 'PLN',
value: 99.99,
items: [
{ item_id: '95769_5455', item_name: 'Sukienka midi', price: 99.99, quantity: 1 }
]
});
Wizualnie podobne, mechanicznie inny świat. gtag('event', ...) to bezpośrednie wywołanie biblioteki Google, które idzie własnym kanałem prosto do property GA4. Ono nie tworzy wpisu w dataLayer, więc Custom Event trigger nie ma czego złapać. Z punktu widzenia GTM tego zdarzenia po prostu nie było.
Stąd objaw: piksel bazowy, który odpala się na surowym page view, żyje, bo nie zależy od funnelowego eventu. A wszystko, co wisi na Custom Event triggerze, milczy, bo te eventy lecą obok GTM, kanałem gtag.
Diagnoza w dwie minuty: co naprawdę jest w dataLayer
Nie trzeba zgadywać. Otwórz konsolę przeglądarki na stronie podziękowania (thank-you page) po testowym zakupie i wpisz:
window.dataLayer.filter(function (e) { return e.ecommerce; });
Jeśli wynikiem jest pusta tablica, a jednocześnie na stronie widać numer zamówienia i potwierdzenie zakupu, masz potwierdzenie diagnozy: dane zamówienia istnieją, ale funnel ecommerce nie trafia do dataLayer. Idzie obok, przez gtag.
Drugi szybki test: w trakcie dodawania do koszyka sprawdź window.dataLayer w konsoli i poszukaj wpisu z event: 'add_to_cart'. Brak takiego wpisu przy jednoczesnym poprawnym działaniu wtyczki GA4 (dane lecą do property, widać je w DebugView) to ten sam wzorzec: gtag tak, dataLayer nie.
Ten test rozdziela dwie zupełnie różne diagnozy. Gdyby dataLayer zawierał obiekt ecommerce, a tagi i tak nie strzelały, problem byłby w triggerach albo w consent. Pusty dataLayer przy żywym numerze zamówienia wskazuje jednoznacznie na drogę gtag i prowadzi prosto do naprawy po stronie panelu Shopera, nie GTM.
Fix: checkbox “Przesyłaj dane za pomocą Google Tag Managera”
W panelu Shopera, w konfiguracji natywnej wtyczki GA4, jest opcja z pozoru niepozorna: “Przesyłaj dane za pomocą Google Tag Managera” (etykieta bywa drobnie inna w zależności od wersji, ale sens ten sam). Domyślnie potrafi być wyłączona, i wtedy wtyczka idzie kanałem gtag.
Po jej zaznaczeniu Shoper zaczyna pchać zdarzenia ecommerce do window.dataLayer w standardowej strukturze GTM, zamiast (albo obok) wysyłać je przez gtag. Efekt jest natychmiastowy: w Preview Mode pojawiają się eventy view_item, add_to_cart, begin_checkout, purchase z obiektem ecommerce, Custom Event triggery zaczynają je łapać, a cały funnel ożywa.
U wspomnianego sklepu modowego ten jeden checkbox przywrócił komplet zdarzeń Meta: ViewContent, AddToCart, InitiateCheckout i Purchase zaczęły strzelać z całą zawartością produktową, podczas gdy wcześniej żył tylko piksel bazowy. Zero zmian w GTM, zero nowych triggerów. Wystarczyło dać im na co reagować.
To moment, w którym warto spiąć obraz z szerszym kontekstem. Jeśli budujesz pełen pomiar od zera, kolejność kroków i zależności między panelem Shopera a kontenerem opisuje kompletny przewodnik po server-side GTM na Shoperze. Tu skupiam się na jednej, konkretnej awarii.
Efekt uboczny: podwójne liczenie w GA4
Włączenie checkboxa rozwiązuje funnel, ale wprowadza drugi problem. Natywna wtyczka GA4 po włączeniu transmisji przez GTM zwykle nie przestaje wysyłać hitów własnym kanałem gtag do skonfigurowanej property GA4. Czyli masz teraz dwa źródła karmiące to samo:
- natywny gtag wtyczki Shopera, lecący wprost do Twojej property GA4,
- Twój tag GA4 w GTM, który teraz odpala się z odżywionego
dataLayer.
Skutek: każdy page_view, każdy add_to_cart, każdy purchase liczony jest dwa razy. Sesje, konwersje, przychód - wszystko zdublowane. W GA4 to widać jako podejrzanie wysokie liczby i rozjazd z panelem sklepu oraz z danymi w Meta.
Pokusa, żeby po prostu wyłączyć natywny gtag całkowicie, jest zrozumiała, ale ryzykowna: to właśnie natywna wtyczka jest źródłem pushy do dataLayer po włączeniu checkboxa. Wyłączając ją w całości, ryzykujesz, że odetniesz sobie funnel, który dopiero co naprawiłeś. Potrzebne jest cięcie chirurgiczne, nie siekiera.
Rozwiązanie double-count: technika dummy measurement ID
Trik polega na rozdzieleniu dwóch funkcji, które natywna wtyczka łączy: emisji hitów GA4 oraz pushowania do dataLayer. Push do dataLayer jest niezależny od tego, jakie measurement ID wpiszesz we wtyczce. Bezpośrednie hity gtag są w pełni zależne od tego ID, bo to ono mówi, do której property je wysłać.
Zatem w natywnej wtyczce GA4 podmieniasz prawdziwe measurement ID na atrapę, na przykład G-0000000000. Co się wtedy dzieje:
- natywny gtag dalej wysyła hity, ale do nieistniejącej, martwej property
G-0000000000, gdzie nikt ich nie zlicza, - push do
dataLayerżyje dalej, bo nie zależy od measurement ID, więc Twój funnel w GTM ma się dobrze, - Twój tag GA4 w GTM, skonfigurowany z prawdziwym measurement ID, zasila wyłącznie prawdziwą property.
W rezultacie prawdziwa property GA4 jest karmiona tylko przez Twój kontener GTM (a docelowo przez Twój serwerowy GTM), a natywny gtag strzela w próżnię. Podwójne liczenie znika, funnel zostaje.
Natywna wtyczka GA4 (measurement ID = G-0000000000)
|
+-- gtag('event', ...) -> property G-0000000000 (martwa, nikt nie liczy)
|
+-- dataLayer.push({ event: 'add_to_cart', ecommerce: {...} })
|
v
Twój GTM -> tag GA4 (prawdziwe G-XXXXXXXXXX) -> prawdziwa property
-> tagi Meta (ViewContent / AddToCart / ...)
U sklepu modowego dummy ID zdjęło podwójne liczenie przy zachowaniu pełnego dataLayer. Property dostawała pojedyncze, czyste hity z GTM, a cały funnel Meta dalej zasilał się z tych samych pushy. Jedna property, jedno źródło prawdy.
RWD: adapter shopLayer do dataLayer i pułapka prefiksowanego item_id
Nawet z włączonym checkboxem zdarza się, że struktura, którą Shoper wypycha do dataLayer, nie jest tym, czego potrzebują tagi Meta i Google co do joty. Na RWD pojawia się też dodatkowa warstwa, shopLayer, którą czasem trzeba zaadaptować ręcznie modułem własnym JS, żeby uzupełnić brakujące zdarzenia albo doczyścić pola.
Najczęstsza pułapka na RWD to prefiksowany item_id. Identyfikator produktu potrafi przyjść w formie złożonej, na przykład "95769_5455", gdzie człon przed podkreśleniem to wewnętrzny identyfikator, a właściwy product_id jest dopiero za nim (albo odwrotnie, zależnie od wariantu). Jeśli prześlesz tę prefiksowaną wartość prosto do item_id, Meta Catalog i Google Merchant Center nie powiążą produktu z katalogiem, bo w katalogu siedzi surowy product_id. Dynamic remarketing przestaje łączyć obejrzane produkty z reklamami.
Naprawa to normalizacja item_id do surowego product_id przed mapowaniem na dataLayer:
// Adapter shopLayer -> dataLayer (modul wlasny JS, Shoper Premium)
// normalizacja prefiksowanego item_id "95769_5455" -> "5455"
function normalizeItemId(raw) {
if (raw == null) return raw;
var s = String(raw);
var idx = s.indexOf('_');
return idx === -1 ? s : s.slice(idx + 1);
}
function pushAddToCart(product) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'add_to_cart',
ecommerce: {
currency: product.currency || 'PLN',
value: product.price,
items: [
{
item_id: normalizeItemId(product.id),
item_name: product.name,
price: product.price,
quantity: product.quantity || 1
}
]
}
});
}
Ustal kierunek prefiksu raz, na podstawie tego, co faktycznie siedzi w katalogu Meta i Merchant Center, i trzymaj go konsekwentnie w całym lejku. Niespójny item_id na ViewContent i AddToCart rozjeżdża dopasowanie do katalogu tak samo skutecznie jak prefiks w obu.
Adapter to też naturalne miejsce, żeby dorzucić wspólny identyfikator zdarzenia, który spina piksel z serwerem i zapobiega podwójnemu liczeniu po stronie Meta. Mechanikę deduplikacji Purchase po numerze zamówienia opisuję w artykule o trzech sposobach na duplikację eventów purchase na Shoperze, a samą emisję funnela do Conversions API w przewodniku po Meta CAPI na Shoperze bez wtyczek.
Pułapka pola “Integracje własne”: łamanie długich linii
Jest jeszcze jedna pułapka, czysto mechaniczna, która potrafi zjeść godzinę debugowania. Pole Integracje własne w panelu Shopera (to, w które wkleja się dodatkowy kod, na przykład snippet GTM albo moduł adaptera) twardo zawija długie linie około 64 znaków. I nie robi tego po spacji czy po przecinku, tylko w dowolnym miejscu, często w środku tokena.
Skutek jest paskudny, bo niewidoczny na pierwszy rzut oka. Wklejasz poprawny, jednolinijkowy snippet, a Shoper łamie go w połowie nazwy zmiennej albo w środku ciągu znaków. Po zapisaniu kod jest składniowo zepsuty, JavaScript się wysypuje, a w panelu wygląda to jak zwykłe, niewinne zawinięcie wiersza. Łatwo przeoczyć, że to nie jest miękkie zawinięcie podglądu, tylko twarda zmiana treści.
Obrona jest prosta: łam kod sam, na krótkie linie, zanim wkleisz. Trzymaj się dwóch zasad:
- każda linia poniżej 64 znaków, najlepiej z zapasem (celuj w okolice 50-60),
- nowa linia tylko w bezpiecznych miejscach: po
;, po,, po{i po}, nigdy w środku nazwy zmiennej, ciągu znaków ani liczby.
Przykład bezpiecznego łamania:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'add_to_cart',
ecommerce: {
currency: 'PLN',
value: 99.99
}
});
Tak złamany kod przetrwa zapis w polu Integracje własne bez uszkodzenia, bo żadna linia nie zbliża się do granicy zawijania, a punkty łamania są neutralne składniowo. Po zapisaniu zawsze warto skopiować kod z powrotem z panelu i porównać z oryginałem, żeby mieć pewność, że nic nie zostało przecięte w środku tokena.
Podsumowanie
Martwy funnel na Shoper Premium RWD, przy poprawnych Custom Event triggerach, to klasyczny przypadek zdarzeń lecących przez gtag('event', ...) zamiast do dataLayer. Triggery nasłuchują dataLayer, gtag idzie obok, więc strzela tylko piksel bazowy. Diagnoza zajmuje dwie minuty: pusty obiekt ecommerce w dataLayer przy żywym numerze zamówienia na stronie podziękowania.
Naprawa jest po stronie panelu Shopera, nie GTM. Checkbox “Przesyłaj dane za pomocą Google Tag Managera” w natywnej wtyczce GA4 ożywia cały funnel, pchając ecommerce do dataLayer. Skutkiem ubocznym jest podwójne liczenie w GA4, które zdejmujesz techniką dummy measurement ID: w natywnej wtyczce wpisujesz G-0000000000, natywny gtag strzela do martwej property, a prawdziwą zasila wyłącznie Twój kontener. Na koniec zostają dwie rzeczy do dopilnowania na RWD: normalizacja prefiksowanego item_id do surowego product_id w adapterze shopLayer do dataLayer, oraz ręczne łamanie kodu na krótkie linie, żeby pole Integracje własne nie przecięło snippetu w środku tokena w okolicy 64 znaków.
To jest dokładnie rodzaj awarii, którą rozbieram w ramach pomiaru server-side dla sklepów na Shoperze - opisanego na stronie tracking Shoper. Jeśli Twój funnel milczy mimo zielonego Tag Assistanta, zacznij od konsoli i window.dataLayer, a nie od triggerów. Tam zwykle leży odpowiedź.