[{"data":1,"prerenderedAt":2214},["ShallowReactive",2],{"wpis-pasek-zgod-na-wlasnym-kodzie":3},{"id":4,"title":5,"body":6,"date":2200,"description":2201,"extension":2202,"kategoria":2203,"meta":2204,"navigation":247,"obrazek":2205,"obrazekAlt":2206,"path":2207,"seo":2208,"sitemap":2209,"stem":2210,"szkic":2211,"zaktualizowano":2212,"__hash__":2213},"wpisy/pasek-zgod-na-wlasnym-kodzie.md","Pasek zgód na własnym kodzie — dlaczego liczy się moment, a nie kilobajty",{"type":7,"value":8,"toc":2183},"minimark",[9,18,21,24,29,32,38,44,48,60,71,74,80,83,86,90,105,120,123,126,133,137,140,161,168,171,175,366,379,389,392,505,508,512,676,690,697,739,753,760,764,767,1176,1182,1187,1299,1306,1309,1313,1316,1327,1332,1335,1338,1341,1386,1392,1398,1402,1408,1411,1414,1518,1525,1528,1532,1535,1541,1547,1554,1561,1568,1575,1578,1581,1585,1588,1612,1615,1744,1751,1774,1777,1955,1962,1983,1986,1996,2119,2129,2132,2136,2139,2145,2151,2157,2163,2166,2170,2173,2176,2179],[10,11,12,13,17],"p",{},"Skrypt wejściowy popularnej platformy zgód waży 121 kB, po gzipie 28,3 kB, i jest tylko loaderem — konfigurację domeny oraz samo okno dialogowe dociąga osobno. Logika zgód, którą napisałem zamiast niego, ma 1922 bajty po minifikacji i ",[14,15,16],"strong",{},"900 bajtów po gzipie",", wchodzi we wspólny bundle i nie dokłada żadnego żądania.",[10,19,20],{},"To porównanie obejmuje po obu stronach samą logikę. Moje 900 bajtów nie zawiera komponentu rysującego pasek, a te 28 kB nie zawiera okna dialogowego platformy. Komponent u mnie to 2,8 kB źródła Vue przed kompresją, które w bundlu i tak dzieli style z resztą serwisu.",[10,22,23],{},"Różnica w kilobajtach jest tu jednak najmniej ciekawa. Skrypt zgód z definicji musi wystartować przed tagami, które ma blokować, więc ląduje w tym miejscu ładowania strony, gdzie każde żądanie konkuruje z treścią. Ten efekt zmierzyłem — choć na innych skryptach niż platforma zgód.",[25,26,28],"h2",{"id":27},"co-jest-pomiarem-a-co-wnioskiem","Co jest pomiarem, a co wnioskiem",[10,30,31],{},"Chcę to powiedzieć na wstępie, żeby nikt nie musiał się domyślać.",[10,33,34,37],{},[14,35,36],{},"Zmierzone:"," rozmiar loadera platformy zgód, rozmiar mojego kodu, oraz koszt dwóch innych skryptów zewnętrznych w oknie krytycznym mojej strony głównej.",[10,39,40,43],{},[14,41,42],{},"Wywnioskowane przez analogię:"," że gotowa platforma zgód kosztuje podobnie. Nie wstawiłem jej na swoją stronę i nie zmierzyłem. Wniosek opieram na tym, że jest większa od tamtych dwóch skryptów i z konieczności siedzi wcześniej. Traktuj to jako hipotezę popartą sąsiednim pomiarem.",[25,45,47],{"id":46},"dziesięć-kilobajtów-które-kosztowały-450-ms","Dziesięć kilobajtów, które kosztowały 450 ms",[10,49,50,51,55,56,59],{},"Na targetx.pl Cloudflare wstrzykiwał własny beacon do statystyk — ",[52,53,54],"code",{},"beacon.min.js",", ",[14,57,58],{},"10 kB"," przy 332 kB całego skryptu strony. Przy tych proporcjach wyglądał na zaokrąglenie.",[10,61,62,63,66,67,70],{},"Przez większość dnia optymalizacji ",[14,64,65],{},"FCP stało nieruchomo na 2701 ms",", powtarzalne co do milisekundy w dwunastu kolejnych przebiegach PageSpeed Insights. Nie drgnęło po zdjęciu ponad 300 kB skryptu. Nie drgnęło po ucięciu 87 kB HTML-a. Ruszyło dopiero po wyłączeniu beacona: ",[14,68,69],{},"2701 → 2251 ms",".",[10,72,73],{},"Tyle pomiaru. Teraz interpretacja, bo mechanizmu nie udowodniłem.",[10,75,76,79],{},[52,77,78],{},"\u003Cscript type=\"module\">"," jest domyślnie odroczony i renderu nie blokuje, więc te 450 ms nie biorą się z blokowania parsera. Najbardziej prawdopodobne wyjaśnienie to koszt otwarcia połączenia do trzeciej domeny — rozwiązanie DNS, uzgodnienie TLS i transfer — w oknie, w którym przeglądarka zajmuje się obrazkiem hero. Lighthouse symuluje wolne łącze, więc dodatkowy handshake waży tam więcej niż na moim biurku. Pewności nie mam; mam za to powtarzalny wynik i moment, w którym się zmienił.",[10,81,82],{},"Jedno zastrzeżenie do samej powtarzalności. PageSpeed Insights liczy FCP modelem Lantern, na podstawie grafu żądań, a nie z prawdziwego przebiegu na łączu. Dwanaście identycznych wyników to więc cecha symulacji, nie dowód stabilności w terenie. Model wzmacnia moją interpretację, bo dolicza koszt dodatkowego połączenia — i jednocześnie znaczy, że te 450 ms jest wynikiem laboratoryjnym. Dane z prawdziwych przeglądarek mogą pokazać mniej albo więcej.",[10,84,85],{},"Wniosek praktyczny zostaje ten sam niezależnie od mechanizmu: przy skryptach ładowanych wcześnie patrz na pozycję w kolejce, nie na rozmiar pliku.",[25,87,89],{"id":88},"druga-historia-poprawna-konfiguracja-która-nic-nie-robiła","Druga historia: poprawna konfiguracja, która nic nie robiła",[10,91,92,93,96,97,100,101,104],{},"Na tej samej stronie miałem włączoną funkcję podającą kontener Tag Managera z pierwszej domeny, spod ścieżki w rodzaju ",[52,94,95],{},"mojadomena.pl/zig4/",". Kosztowała około 300 kB, ale gorsze było co innego: wstawka ",[52,98,99],{},"google_tags_first_party"," siedziała na ",[14,102,103],{},"bajcie 151"," dokumentu HTML.",[10,106,107,108,111,112,115,116,119],{},"Kontener startował więc przed hydracją aplikacji, a mój kod ustawiający domyślne odmowy zgód trafiał do ",[52,109,110],{},"dataLayer"," dopiero po zdarzeniach ",[52,113,114],{},"gtm.dom"," i ",[52,117,118],{},"gtm.load",". Consent Mode wymaga odwrotnej kolejności — domyślne ustawienia muszą być na miejscu przed startem kontenera, bo inaczej tagi czytają stan, którego jeszcze nie ustawiłeś.",[10,121,122],{},"W panelu wszystko wyglądało poprawnie. W przeglądarce mechanizm zgód był pozorny.",[10,124,125],{},"Po wyłączeniu tej funkcji wynik ogólny poszedł w górę: Performance 70 → 81–85, LCP 5,6 s → 3,35 s. FCP, jak wyżej, nie zareagowało wcale — stąd dwie osobne historie zamiast jednej.",[10,127,128,129,132],{},"Dla porządku co do liczb: 332 kB skryptu to stan ",[14,130,131],{},"po"," wyłączeniu tej funkcji. Beacon mierzyłem już w tym stanie.",[25,134,136],{"id":135},"co-taki-pasek-musi-umieć","Co taki pasek musi umieć",[10,138,139],{},"Zanim napiszesz własny, warto wiedzieć, na co się piszesz. Od strony technicznej:",[141,142,143,147,150,155,158],"ol",{},[144,145,146],"li",{},"Ustawić domyślne odmowy w Consent Mode v2, zanim wystartuje kontener.",[144,148,149],{},"Pokazać wybór, gdy decyzji jeszcze nie ma.",[144,151,152,153,70],{},"Zapamiętać decyzję i przekazać ją do ",[52,154,110],{},[144,156,157],{},"Pozwolić ją wycofać, ze skutkiem natychmiastowym.",[144,159,160],{},"Nie pokazywać się nikomu, kto już zdecydował.",[10,162,163,164,167],{},"Od strony prawnej dochodzą dwa wymogi dotyczące samego interfejsu, o których łatwo zapomnieć przy pisaniu własnego. Przycisk odmowy musi być ",[14,165,166],{},"równorzędny"," z przyciskiem zgody — ten sam poziom, podobna widoczność, bez chowania odmowy w ustawieniach szczegółowych. I żadna zgoda nie może być domyślnie zaznaczona; brak decyzji znaczy odmowę.",[10,169,170],{},"Pasek z jednym przyciskiem „OK\" łamie pierwszy warunek wprost, a drugi omija, bo nie daje żadnego wyboru. Widuje się go wciąż często.",[25,172,174],{"id":173},"domyślne-odmowy","Domyślne odmowy",[176,177,182],"pre",{"className":178,"code":179,"language":180,"meta":181,"style":181},"language-ts shiki shiki-themes one-dark-pro","const gtag: Gtag = function () {\n  warstwa().push(arguments);\n};\n\nexport function ustawDomyslneZgody(): void {\n  gtag('consent', 'default', {\n    ad_storage: 'denied',\n    ad_user_data: 'denied',\n    ad_personalization: 'denied',\n    analytics_storage: 'denied',\n    wait_for_update: 500,\n  });\n}\n","ts","",[52,183,184,215,236,242,249,269,289,304,316,328,340,354,360],{"__ignoreMap":181},[185,186,189,193,197,201,205,209,212],"span",{"class":187,"line":188},"line",1,[185,190,192],{"class":191},"seHd6","const",[185,194,196],{"class":195},"sVbv2"," gtag",[185,198,200],{"class":199},"sn6KH",": ",[185,202,204],{"class":203},"sU0A5","Gtag",[185,206,208],{"class":207},"sjrmR"," =",[185,210,211],{"class":191}," function",[185,213,214],{"class":199}," () {\n",[185,216,218,221,224,227,230,233],{"class":187,"line":217},2,[185,219,220],{"class":195},"  warstwa",[185,222,223],{"class":199},"().",[185,225,226],{"class":195},"push",[185,228,229],{"class":199},"(",[185,231,232],{"class":203},"arguments",[185,234,235],{"class":199},");\n",[185,237,239],{"class":187,"line":238},3,[185,240,241],{"class":199},"};\n",[185,243,245],{"class":187,"line":244},4,[185,246,248],{"emptyLinePlaceholder":247},true,"\n",[185,250,252,255,257,260,263,266],{"class":187,"line":251},5,[185,253,254],{"class":191},"export",[185,256,211],{"class":191},[185,258,259],{"class":195}," ustawDomyslneZgody",[185,261,262],{"class":199},"(): ",[185,264,265],{"class":203},"void",[185,267,268],{"class":199}," {\n",[185,270,272,275,277,281,283,286],{"class":187,"line":271},6,[185,273,274],{"class":195},"  gtag",[185,276,229],{"class":199},[185,278,280],{"class":279},"subq3","'consent'",[185,282,55],{"class":199},[185,284,285],{"class":279},"'default'",[185,287,288],{"class":199},", {\n",[185,290,292,296,298,301],{"class":187,"line":291},7,[185,293,295],{"class":294},"sVyAn","    ad_storage",[185,297,200],{"class":199},[185,299,300],{"class":279},"'denied'",[185,302,303],{"class":199},",\n",[185,305,307,310,312,314],{"class":187,"line":306},8,[185,308,309],{"class":294},"    ad_user_data",[185,311,200],{"class":199},[185,313,300],{"class":279},[185,315,303],{"class":199},[185,317,319,322,324,326],{"class":187,"line":318},9,[185,320,321],{"class":294},"    ad_personalization",[185,323,200],{"class":199},[185,325,300],{"class":279},[185,327,303],{"class":199},[185,329,331,334,336,338],{"class":187,"line":330},10,[185,332,333],{"class":294},"    analytics_storage",[185,335,200],{"class":199},[185,337,300],{"class":279},[185,339,303],{"class":199},[185,341,343,346,348,352],{"class":187,"line":342},11,[185,344,345],{"class":294},"    wait_for_update",[185,347,200],{"class":199},[185,349,351],{"class":350},"sVC51","500",[185,353,303],{"class":199},[185,355,357],{"class":187,"line":356},12,[185,358,359],{"class":199},"  });\n",[185,361,363],{"class":187,"line":362},13,[185,364,365],{"class":199},"}\n",[10,367,368,369,372,373,375,376,378],{},"Funkcja ",[52,370,371],{},"gtag"," wypycha do ",[52,374,110],{}," obiekt ",[52,377,232],{},", a nie zwykłą tablicę. Tak wygląda oryginalna wtyczka Google'a i tak trzeba to zrobić, bo kontener rozpoznaje polecenia zgód po tym kształcie.",[10,380,381,384,385,388],{},[52,382,383],{},"wait_for_update: 500"," każe tagom wstrzymać się pół sekundy na wypadek, gdyby zaraz przyszła aktualizacja odczytana z ciasteczka. W mojej architekturze jest to asekuracja, a nie mechanizm, na którym polegam: kontener startuje dopiero po zdarzeniu ",[52,386,387],{},"load",", więc odczyt z ciasteczka i tak trafia do kolejki wcześniej. Zostawiam to pole na wypadek, gdyby ktoś przestawił moment startu.",[10,390,391],{},"Aktualizacja po decyzji podnosi wyłącznie jedną flagę:",[176,393,395],{"className":178,"code":394,"language":180,"meta":181,"style":181},"export function zaktualizujZgode(wybor: WyborZgody): void {\n  gtag('consent', 'update', {\n    ad_storage: 'denied',\n    ad_user_data: 'denied',\n    ad_personalization: 'denied',\n    analytics_storage: wybor === 'udzielona' ? 'granted' : 'denied',\n  });\n}\n",[52,396,397,424,439,449,459,469,497,501],{"__ignoreMap":181},[185,398,399,401,403,406,408,412,414,417,420,422],{"class":187,"line":188},[185,400,254],{"class":191},[185,402,211],{"class":191},[185,404,405],{"class":195}," zaktualizujZgode",[185,407,229],{"class":199},[185,409,411],{"class":410},"s_ZVi","wybor",[185,413,200],{"class":199},[185,415,416],{"class":203},"WyborZgody",[185,418,419],{"class":199},"): ",[185,421,265],{"class":203},[185,423,268],{"class":199},[185,425,426,428,430,432,434,437],{"class":187,"line":217},[185,427,274],{"class":195},[185,429,229],{"class":199},[185,431,280],{"class":279},[185,433,55],{"class":199},[185,435,436],{"class":279},"'update'",[185,438,288],{"class":199},[185,440,441,443,445,447],{"class":187,"line":238},[185,442,295],{"class":294},[185,444,200],{"class":199},[185,446,300],{"class":279},[185,448,303],{"class":199},[185,450,451,453,455,457],{"class":187,"line":244},[185,452,309],{"class":294},[185,454,200],{"class":199},[185,456,300],{"class":279},[185,458,303],{"class":199},[185,460,461,463,465,467],{"class":187,"line":251},[185,462,321],{"class":294},[185,464,200],{"class":199},[185,466,300],{"class":279},[185,468,303],{"class":199},[185,470,471,473,475,477,480,483,486,489,492,495],{"class":187,"line":271},[185,472,333],{"class":294},[185,474,200],{"class":199},[185,476,411],{"class":294},[185,478,479],{"class":207}," ===",[185,481,482],{"class":279}," 'udzielona'",[185,484,485],{"class":191}," ?",[185,487,488],{"class":279}," 'granted'",[185,490,491],{"class":191}," :",[185,493,494],{"class":279}," 'denied'",[185,496,303],{"class":199},[185,498,499],{"class":187,"line":291},[185,500,359],{"class":199},[185,502,503],{"class":187,"line":306},[185,504,365],{"class":199},[10,506,507],{},"Trzy pozostałe zostają odrzucone niezależnie od wyboru, bo na tych serwisach nie ma reklam. Gdy są, dwa przyciski przestają wystarczać: potrzebujesz osobnych kategorii i ekranu szczegółowego, a to jest moment, w którym własne rozwiązanie zaczyna się robić projektem samym w sobie.",[25,509,511],{"id":510},"zapis-i-wycofanie","Zapis i wycofanie",[176,513,515],{"className":178,"code":514,"language":180,"meta":181,"style":181},"const NAZWA_CIASTECZKA = 'targetx_zgody';\nconst WAZNOSC_DNI = 180;\n\nexport function zapiszZgode(wybor: WyborZgody): void {\n  const bezpieczne = location.protocol === 'https:' ? '; Secure' : '';\n  document.cookie = `${NAZWA_CIASTECZKA}=${wybor}; path=/; max-age=${WAZNOSC_DNI * 86400}; SameSite=Lax${bezpieczne}`;\n}\n",[52,516,517,532,546,550,573,608,672],{"__ignoreMap":181},[185,518,519,521,524,526,529],{"class":187,"line":188},[185,520,192],{"class":191},[185,522,523],{"class":203}," NAZWA_CIASTECZKA",[185,525,208],{"class":207},[185,527,528],{"class":279}," 'targetx_zgody'",[185,530,531],{"class":199},";\n",[185,533,534,536,539,541,544],{"class":187,"line":217},[185,535,192],{"class":191},[185,537,538],{"class":203}," WAZNOSC_DNI",[185,540,208],{"class":207},[185,542,543],{"class":350}," 180",[185,545,531],{"class":199},[185,547,548],{"class":187,"line":238},[185,549,248],{"emptyLinePlaceholder":247},[185,551,552,554,556,559,561,563,565,567,569,571],{"class":187,"line":244},[185,553,254],{"class":191},[185,555,211],{"class":191},[185,557,558],{"class":195}," zapiszZgode",[185,560,229],{"class":199},[185,562,411],{"class":410},[185,564,200],{"class":199},[185,566,416],{"class":203},[185,568,419],{"class":199},[185,570,265],{"class":203},[185,572,268],{"class":199},[185,574,575,578,581,583,586,588,591,593,596,598,601,603,606],{"class":187,"line":251},[185,576,577],{"class":191},"  const",[185,579,580],{"class":203}," bezpieczne",[185,582,208],{"class":207},[185,584,585],{"class":203}," location",[185,587,70],{"class":199},[185,589,590],{"class":294},"protocol",[185,592,479],{"class":207},[185,594,595],{"class":279}," 'https:'",[185,597,485],{"class":191},[185,599,600],{"class":279}," '; Secure'",[185,602,491],{"class":191},[185,604,605],{"class":279}," ''",[185,607,531],{"class":199},[185,609,610,613,615,618,620,623,626,629,632,635,637,639,641,644,646,649,652,655,657,660,662,665,667,670],{"class":187,"line":271},[185,611,612],{"class":203},"  document",[185,614,70],{"class":199},[185,616,617],{"class":294},"cookie",[185,619,208],{"class":207},[185,621,622],{"class":279}," `",[185,624,625],{"class":191},"${",[185,627,628],{"class":203},"NAZWA_CIASTECZKA",[185,630,631],{"class":191},"}",[185,633,634],{"class":279},"=",[185,636,625],{"class":191},[185,638,411],{"class":294},[185,640,631],{"class":191},[185,642,643],{"class":279},"; path=/; max-age=",[185,645,625],{"class":191},[185,647,648],{"class":203},"WAZNOSC_DNI",[185,650,651],{"class":207}," *",[185,653,654],{"class":350}," 86400",[185,656,631],{"class":191},[185,658,659],{"class":279},"; SameSite=Lax",[185,661,625],{"class":191},[185,663,664],{"class":294},"bezpieczne",[185,666,631],{"class":191},[185,668,669],{"class":279},"`",[185,671,531],{"class":199},[185,673,674],{"class":187,"line":291},[185,675,365],{"class":199},[10,677,678,679,682,683,686,687,70],{},"Zwykłe ciasteczko, bez biblioteki. ",[52,680,681],{},"SameSite=Lax"," wystarcza, bo nikt nie czyta tego z żądania między domenami, a ",[52,684,685],{},"Secure"," dokładam warunkowo, żeby mechanizm działał też pod ",[52,688,689],{},"localhost",[10,691,692,693,696],{},"Wycofanie zgody wymaga ",[14,694,695],{},"trzech"," rzeczy, a nie jednej — i tu popełniłem błąd, który wyłapała dopiero recenzja tego wpisu. Moja pierwsza wersja kasowała ciasteczko wyboru i pokazywała pasek, przez co analityka chodziła dalej na udzielonej zgodzie aż do końca bieżącej odsłony. Poprawnie:",[176,698,700],{"className":178,"code":699,"language":180,"meta":181,"style":181},"zapomnijZgode();\nzaktualizujZgode('odrzucona');\nwidoczny.value = true;\n",[52,701,702,710,722],{"__ignoreMap":181},[185,703,704,707],{"class":187,"line":188},[185,705,706],{"class":195},"zapomnijZgode",[185,708,709],{"class":199},"();\n",[185,711,712,715,717,720],{"class":187,"line":217},[185,713,714],{"class":195},"zaktualizujZgode",[185,716,229],{"class":199},[185,718,719],{"class":279},"'odrzucona'",[185,721,235],{"class":199},[185,723,724,727,729,732,734,737],{"class":187,"line":238},[185,725,726],{"class":203},"widoczny",[185,728,70],{"class":199},[185,730,731],{"class":294},"value",[185,733,208],{"class":207},[185,735,736],{"class":350}," true",[185,738,531],{"class":199},[10,740,741,742,115,745,748,749,752],{},"Czego nadal nie robię: ciasteczka ",[52,743,744],{},"_ga",[52,746,747],{},"_ga_*"," zostają w przeglądarce. Po odmowie analityka do nich nie sięga, więc są bezużyteczne, ale fizycznie tam są. Skasowanie ich wymaga odgadnięcia domeny, na której zostały ustawione, co przy adresach w rodzaju ",[52,750,751],{},"co.uk"," bywa zawodne. Jeśli robisz to u siebie, sprawdź, na jakiej domenie faktycznie siedzą.",[10,754,755,756,759],{},"Sam przełącznik w stopce to link z atrybutem ",[52,757,758],{},"data-zmien-zgody",", a komponent nasłuchuje kliknięć na całym dokumencie i reaguje na najbliższy pasujący element. Dzięki temu link można wstawić w dowolnym miejscu układu bez przekazywania czegokolwiek przez propsy.",[25,761,763],{"id":762},"kiedy-startuje-tag-manager","Kiedy startuje Tag Manager",[10,765,766],{},"Tu leży druga połowa zysku na wydajności:",[176,768,770],{"className":178,"code":769,"language":180,"meta":181,"style":181},"const LIMIT_BEZCZYNNOSCI_MS = 3000;\nconst ZAPASOWE_OPOZNIENIE_MS = 2500;\n\nconst odpal = () => zaladujGtm(identyfikator);\n\nconst poBezczynnosci = () => {\n  const okno = window as Window & {\n    requestIdleCallback?: (wywolanie: () => void, opcje?: { timeout: number }) => void;\n  };\n\n  if (typeof okno.requestIdleCallback === 'function') {\n    okno.requestIdleCallback(odpal, { timeout: LIMIT_BEZCZYNNOSCI_MS });\n    return;\n  }\n\n  setTimeout(odpal, ZAPASOWE_OPOZNIENIE_MS);\n};\n\nif (document.readyState === 'complete') {\n  poBezczynnosci();\n} else {\n  window.addEventListener('load', poBezczynnosci, { once: true });\n}\n\nwindow.addEventListener('pointerdown', odpal, { once: true, passive: true });\nwindow.addEventListener('keydown', odpal, { once: true });\n",[52,771,772,786,800,804,829,833,848,869,918,923,927,953,980,987,993,998,1015,1020,1025,1048,1056,1067,1100,1105,1110,1148],{"__ignoreMap":181},[185,773,774,776,779,781,784],{"class":187,"line":188},[185,775,192],{"class":191},[185,777,778],{"class":203}," LIMIT_BEZCZYNNOSCI_MS",[185,780,208],{"class":207},[185,782,783],{"class":350}," 3000",[185,785,531],{"class":199},[185,787,788,790,793,795,798],{"class":187,"line":217},[185,789,192],{"class":191},[185,791,792],{"class":203}," ZAPASOWE_OPOZNIENIE_MS",[185,794,208],{"class":207},[185,796,797],{"class":350}," 2500",[185,799,531],{"class":199},[185,801,802],{"class":187,"line":238},[185,803,248],{"emptyLinePlaceholder":247},[185,805,806,808,811,813,816,819,822,824,827],{"class":187,"line":244},[185,807,192],{"class":191},[185,809,810],{"class":195}," odpal",[185,812,208],{"class":207},[185,814,815],{"class":199}," () ",[185,817,818],{"class":191},"=>",[185,820,821],{"class":195}," zaladujGtm",[185,823,229],{"class":199},[185,825,826],{"class":294},"identyfikator",[185,828,235],{"class":199},[185,830,831],{"class":187,"line":251},[185,832,248],{"emptyLinePlaceholder":247},[185,834,835,837,840,842,844,846],{"class":187,"line":271},[185,836,192],{"class":191},[185,838,839],{"class":195}," poBezczynnosci",[185,841,208],{"class":207},[185,843,815],{"class":199},[185,845,818],{"class":191},[185,847,268],{"class":199},[185,849,850,852,855,857,860,863,866],{"class":187,"line":291},[185,851,577],{"class":191},[185,853,854],{"class":203}," okno",[185,856,208],{"class":207},[185,858,859],{"class":294}," window",[185,861,862],{"class":191}," as",[185,864,865],{"class":203}," Window",[185,867,868],{"class":199}," & {\n",[185,870,871,874,877,880,883,886,888,891,893,896,898,901,904,906,909,912,914,916],{"class":187,"line":306},[185,872,873],{"class":195},"    requestIdleCallback",[185,875,876],{"class":191},"?",[185,878,879],{"class":199},": (",[185,881,882],{"class":195},"wywolanie",[185,884,885],{"class":199},": () ",[185,887,818],{"class":191},[185,889,890],{"class":203}," void",[185,892,55],{"class":199},[185,894,895],{"class":410},"opcje",[185,897,876],{"class":191},[185,899,900],{"class":199},": { ",[185,902,903],{"class":294},"timeout",[185,905,200],{"class":199},[185,907,908],{"class":203},"number",[185,910,911],{"class":199}," }) ",[185,913,818],{"class":191},[185,915,890],{"class":203},[185,917,531],{"class":199},[185,919,920],{"class":187,"line":318},[185,921,922],{"class":199},"  };\n",[185,924,925],{"class":187,"line":330},[185,926,248],{"emptyLinePlaceholder":247},[185,928,929,932,935,938,940,942,945,947,950],{"class":187,"line":342},[185,930,931],{"class":191},"  if",[185,933,934],{"class":199}," (",[185,936,937],{"class":191},"typeof",[185,939,854],{"class":203},[185,941,70],{"class":199},[185,943,944],{"class":294},"requestIdleCallback",[185,946,479],{"class":207},[185,948,949],{"class":279}," 'function'",[185,951,952],{"class":199},") {\n",[185,954,955,958,960,962,964,967,970,972,974,977],{"class":187,"line":356},[185,956,957],{"class":203},"    okno",[185,959,70],{"class":199},[185,961,944],{"class":195},[185,963,229],{"class":199},[185,965,966],{"class":294},"odpal",[185,968,969],{"class":199},", { ",[185,971,903],{"class":294},[185,973,200],{"class":199},[185,975,976],{"class":203},"LIMIT_BEZCZYNNOSCI_MS",[185,978,979],{"class":199}," });\n",[185,981,982,985],{"class":187,"line":362},[185,983,984],{"class":191},"    return",[185,986,531],{"class":199},[185,988,990],{"class":187,"line":989},14,[185,991,992],{"class":199},"  }\n",[185,994,996],{"class":187,"line":995},15,[185,997,248],{"emptyLinePlaceholder":247},[185,999,1001,1004,1006,1008,1010,1013],{"class":187,"line":1000},16,[185,1002,1003],{"class":195},"  setTimeout",[185,1005,229],{"class":199},[185,1007,966],{"class":294},[185,1009,55],{"class":199},[185,1011,1012],{"class":203},"ZAPASOWE_OPOZNIENIE_MS",[185,1014,235],{"class":199},[185,1016,1018],{"class":187,"line":1017},17,[185,1019,241],{"class":199},[185,1021,1023],{"class":187,"line":1022},18,[185,1024,248],{"emptyLinePlaceholder":247},[185,1026,1028,1031,1033,1036,1038,1041,1043,1046],{"class":187,"line":1027},19,[185,1029,1030],{"class":191},"if",[185,1032,934],{"class":199},[185,1034,1035],{"class":203},"document",[185,1037,70],{"class":199},[185,1039,1040],{"class":294},"readyState",[185,1042,479],{"class":207},[185,1044,1045],{"class":279}," 'complete'",[185,1047,952],{"class":199},[185,1049,1051,1054],{"class":187,"line":1050},20,[185,1052,1053],{"class":195},"  poBezczynnosci",[185,1055,709],{"class":199},[185,1057,1059,1062,1065],{"class":187,"line":1058},21,[185,1060,1061],{"class":199},"} ",[185,1063,1064],{"class":191},"else",[185,1066,268],{"class":199},[185,1068,1070,1073,1075,1078,1080,1083,1085,1088,1090,1093,1095,1098],{"class":187,"line":1069},22,[185,1071,1072],{"class":203},"  window",[185,1074,70],{"class":199},[185,1076,1077],{"class":195},"addEventListener",[185,1079,229],{"class":199},[185,1081,1082],{"class":279},"'load'",[185,1084,55],{"class":199},[185,1086,1087],{"class":294},"poBezczynnosci",[185,1089,969],{"class":199},[185,1091,1092],{"class":294},"once",[185,1094,200],{"class":199},[185,1096,1097],{"class":350},"true",[185,1099,979],{"class":199},[185,1101,1103],{"class":187,"line":1102},23,[185,1104,365],{"class":199},[185,1106,1108],{"class":187,"line":1107},24,[185,1109,248],{"emptyLinePlaceholder":247},[185,1111,1113,1116,1118,1120,1122,1125,1127,1129,1131,1133,1135,1137,1139,1142,1144,1146],{"class":187,"line":1112},25,[185,1114,1115],{"class":203},"window",[185,1117,70],{"class":199},[185,1119,1077],{"class":195},[185,1121,229],{"class":199},[185,1123,1124],{"class":279},"'pointerdown'",[185,1126,55],{"class":199},[185,1128,966],{"class":294},[185,1130,969],{"class":199},[185,1132,1092],{"class":294},[185,1134,200],{"class":199},[185,1136,1097],{"class":350},[185,1138,55],{"class":199},[185,1140,1141],{"class":294},"passive",[185,1143,200],{"class":199},[185,1145,1097],{"class":350},[185,1147,979],{"class":199},[185,1149,1151,1153,1155,1157,1159,1162,1164,1166,1168,1170,1172,1174],{"class":187,"line":1150},26,[185,1152,1115],{"class":203},[185,1154,70],{"class":199},[185,1156,1077],{"class":195},[185,1158,229],{"class":199},[185,1160,1161],{"class":279},"'keydown'",[185,1163,55],{"class":199},[185,1165,966],{"class":294},[185,1167,969],{"class":199},[185,1169,1092],{"class":294},[185,1171,200],{"class":199},[185,1173,1097],{"class":350},[185,1175,979],{"class":199},[10,1177,1178,1179,1181],{},"Kontener czeka na zdarzenie ",[52,1180,387],{},", potem na pierwszą wolną chwilę głównego wątku, z twardym limitem trzech sekund. Równolegle wisi nasłuch na pierwszej interakcji, więc ktoś, kto od razu klika, nie czeka.",[10,1183,1184,1186],{},[52,1185,966],{}," może więc zostać wywołane z trzech miejsc. Przed podwójnym wstrzyknięciem kontenera chroni flaga po stronie funkcji ładującej:",[176,1188,1190],{"className":178,"code":1189,"language":180,"meta":181,"style":181},"export function zaladujGtm(identyfikator: string): void {\n  const okno = window as OknoZWarstwa;\n\n  if (identyfikator === '' || okno.targetxGtmZaladowany) {\n    return;\n  }\n\n  okno.targetxGtmZaladowany = true;\n  // dalej: wstawienie skryptu\n}\n",[52,1191,1192,1215,1232,1236,1260,1266,1270,1274,1289,1295],{"__ignoreMap":181},[185,1193,1194,1196,1198,1200,1202,1204,1206,1209,1211,1213],{"class":187,"line":188},[185,1195,254],{"class":191},[185,1197,211],{"class":191},[185,1199,821],{"class":195},[185,1201,229],{"class":199},[185,1203,826],{"class":410},[185,1205,200],{"class":199},[185,1207,1208],{"class":203},"string",[185,1210,419],{"class":199},[185,1212,265],{"class":203},[185,1214,268],{"class":199},[185,1216,1217,1219,1221,1223,1225,1227,1230],{"class":187,"line":217},[185,1218,577],{"class":191},[185,1220,854],{"class":203},[185,1222,208],{"class":207},[185,1224,859],{"class":294},[185,1226,862],{"class":191},[185,1228,1229],{"class":203}," OknoZWarstwa",[185,1231,531],{"class":199},[185,1233,1234],{"class":187,"line":238},[185,1235,248],{"emptyLinePlaceholder":247},[185,1237,1238,1240,1242,1244,1246,1248,1251,1253,1255,1258],{"class":187,"line":244},[185,1239,931],{"class":191},[185,1241,934],{"class":199},[185,1243,826],{"class":294},[185,1245,479],{"class":207},[185,1247,605],{"class":279},[185,1249,1250],{"class":207}," ||",[185,1252,854],{"class":203},[185,1254,70],{"class":199},[185,1256,1257],{"class":294},"targetxGtmZaladowany",[185,1259,952],{"class":199},[185,1261,1262,1264],{"class":187,"line":251},[185,1263,984],{"class":191},[185,1265,531],{"class":199},[185,1267,1268],{"class":187,"line":271},[185,1269,992],{"class":199},[185,1271,1272],{"class":187,"line":291},[185,1273,248],{"emptyLinePlaceholder":247},[185,1275,1276,1279,1281,1283,1285,1287],{"class":187,"line":306},[185,1277,1278],{"class":203},"  okno",[185,1280,70],{"class":199},[185,1282,1257],{"class":294},[185,1284,208],{"class":207},[185,1286,736],{"class":350},[185,1288,531],{"class":199},[185,1290,1291],{"class":187,"line":318},[185,1292,1294],{"class":1293},"sV9Aq","  // dalej: wstawienie skryptu\n",[185,1296,1297],{"class":187,"line":330},[185,1298,365],{"class":199},[10,1300,1301,1302,1305],{},"Domyślne zgody ustawiam ",[14,1303,1304],{},"przed"," tym wszystkim, w pierwszej linii pluginu. To jest ta kolejność, którą psuła funkcja opisana wcześniej.",[10,1307,1308],{},"Warto uczciwie dodać: samo opóźnienie startu Tag Managera da się zrobić także z gotową platformą zgód. To dwie niezależne decyzje i zysk z nich też jest niezależny. Własny pasek daje kontrolę nad jednym skryptem mniej w oknie krytycznym, opóźnienie GTM-u daje resztę.",[25,1310,1312],{"id":1311},"trzy-pułapki-których-nikt-za-ciebie-nie-obsłuży","Trzy pułapki, których nikt za Ciebie nie obsłuży",[10,1314,1315],{},"Gotowa platforma bierze na siebie także to, co niżej. Przy własnym kodzie trafiasz na to osobiście — ja trafiłem na wszystkie trzy w ciągu jednego popołudnia.",[10,1317,1318,1319,1322,1323,1326],{},"Żeby dalsze przykłady dało się czytać, dwa zdania o tym, co mam w kontenerze. ",[14,1320,1321],{},"Google Analytics 4"," działa w trybie zaawansowanym i ma jeden wyzwalacz, zdarzenie inicjujące. ",[14,1324,1325],{},"Microsoft Clarity",", czyli mapy ciepła i nagrania sesji, działa w trybie podstawowym i wymaga zgody, więc dostał dwa wyzwalacze oraz ustawienie „Raz na stronę\". Te same dwa tagi zobaczysz niżej w JSON-ie kontenera.",[1328,1329,1331],"h3",{"id":1330},"tag-wymagający-zgody-nie-policzy-wizyty-w-której-zgoda-padła","Tag wymagający zgody nie policzy wizyty, w której zgoda padła",[10,1333,1334],{},"Jeśli w Tag Managerze zaznaczysz tagowi „Wymagaj dodatkowej zgody na uruchomienie\", a wyzwalaczem jest zdarzenie inicjujące albo odsłona strony, przy pierwszej wizycie dzieje się tak: kontener startuje, tag zostaje zablokowany brakiem zgody, użytkownik czyta stronę, klika „Zgadzam się\" — i nic się nie dzieje. Wyzwalające zdarzenie dawno minęło, a tag odpala się dopiero przy następnej odsłonie.",[10,1336,1337],{},"Tracisz przez to odsłonę wejścia, czyli tę jedyną, która niesie źródło ruchu.",[10,1339,1340],{},"Poprawka po stronie kodu to jedno zdarzenie:",[176,1342,1344],{"className":178,"code":1343,"language":180,"meta":181,"style":181},"export function zglosUdzielenieZgody(): void {\n  warstwa().push({ event: 'consent_granted' });\n}\n",[52,1345,1346,1361,1382],{"__ignoreMap":181},[185,1347,1348,1350,1352,1355,1357,1359],{"class":187,"line":188},[185,1349,254],{"class":191},[185,1351,211],{"class":191},[185,1353,1354],{"class":195}," zglosUdzielenieZgody",[185,1356,262],{"class":199},[185,1358,265],{"class":203},[185,1360,268],{"class":199},[185,1362,1363,1365,1367,1369,1372,1375,1377,1380],{"class":187,"line":217},[185,1364,220],{"class":195},[185,1366,223],{"class":199},[185,1368,226],{"class":195},[185,1370,1371],{"class":199},"({ ",[185,1373,1374],{"class":294},"event",[185,1376,200],{"class":199},[185,1378,1379],{"class":279},"'consent_granted'",[185,1381,979],{"class":199},[185,1383,1384],{"class":187,"line":238},[185,1385,365],{"class":199},[10,1387,1388,1389,70],{},"Po stronie kontenera dokładasz temu tagowi drugi wyzwalacz typu „zdarzenie niestandardowe\" o nazwie ",[52,1390,1391],{},"consent_granted",[10,1393,1394,1397],{},[14,1395,1396],{},"Razem z tym przestaw „Opcje uruchamiania tagów\" na „Raz na stronę\"."," Domyślne „Raz na zdarzenie\" ogranicza tag do jednego uruchomienia w obrębie jednego zdarzenia, a tutaj masz dwa różne zdarzenia, więc przed dublowaniem nie chroni wcale. Kolejna sekcja pokazuje, jak to wygląda w praktyce.",[1328,1399,1401],{"id":1400},"ta-sama-poprawka-potrafi-liczyć-podwójnie","Ta sama poprawka potrafi liczyć podwójnie",[10,1403,1404,1405,1407],{},"Pierwsza wersja, którą napisałem, wypychała to zdarzenie wewnątrz ",[52,1406,714],{},". Wyglądało logicznie: zgoda się zmienia, więc informuję o tym kontener.",[10,1409,1410],{},"Problem w tym, że ta funkcja woła się w dwóch miejscach. Raz przy kliknięciu w pasku, drugi raz przy starcie strony, gdy plugin odczytuje zapisaną zgodę z ciasteczka. Przy każdym powrocie stałego czytelnika tag odpalał się więc dwa razy: raz ze zdarzenia inicjującego, raz z mojego. Ustawienie „Raz na zdarzenie\" tego nie zatrzymało, bo to były dwa osobne zdarzenia.",[10,1412,1413],{},"Zdarzenie należy do gestu, a nie do stanu. Wypycham je wyłącznie z obsługi kliknięcia:",[176,1415,1417],{"className":178,"code":1416,"language":180,"meta":181,"style":181},"const rozstrzygnij = (wybor: WyborZgody) => {\n  zapiszZgode(wybor);\n  zaktualizujZgode(wybor);\n\n  if (wybor === 'udzielona') {\n    zglosUdzielenieZgody();\n  }\n\n  widoczny.value = false;\n};\n",[52,1418,1419,1443,1454,1465,1469,1483,1490,1494,1498,1514],{"__ignoreMap":181},[185,1420,1421,1423,1426,1428,1430,1432,1434,1436,1439,1441],{"class":187,"line":188},[185,1422,192],{"class":191},[185,1424,1425],{"class":195}," rozstrzygnij",[185,1427,208],{"class":207},[185,1429,934],{"class":199},[185,1431,411],{"class":410},[185,1433,200],{"class":199},[185,1435,416],{"class":203},[185,1437,1438],{"class":199},") ",[185,1440,818],{"class":191},[185,1442,268],{"class":199},[185,1444,1445,1448,1450,1452],{"class":187,"line":217},[185,1446,1447],{"class":195},"  zapiszZgode",[185,1449,229],{"class":199},[185,1451,411],{"class":294},[185,1453,235],{"class":199},[185,1455,1456,1459,1461,1463],{"class":187,"line":238},[185,1457,1458],{"class":195},"  zaktualizujZgode",[185,1460,229],{"class":199},[185,1462,411],{"class":294},[185,1464,235],{"class":199},[185,1466,1467],{"class":187,"line":244},[185,1468,248],{"emptyLinePlaceholder":247},[185,1470,1471,1473,1475,1477,1479,1481],{"class":187,"line":251},[185,1472,931],{"class":191},[185,1474,934],{"class":199},[185,1476,411],{"class":294},[185,1478,479],{"class":207},[185,1480,482],{"class":279},[185,1482,952],{"class":199},[185,1484,1485,1488],{"class":187,"line":271},[185,1486,1487],{"class":195},"    zglosUdzielenieZgody",[185,1489,709],{"class":199},[185,1491,1492],{"class":187,"line":291},[185,1493,992],{"class":199},[185,1495,1496],{"class":187,"line":306},[185,1497,248],{"emptyLinePlaceholder":247},[185,1499,1500,1503,1505,1507,1509,1512],{"class":187,"line":318},[185,1501,1502],{"class":203},"  widoczny",[185,1504,70],{"class":199},[185,1506,731],{"class":294},[185,1508,208],{"class":207},[185,1510,1511],{"class":350}," false",[185,1513,531],{"class":199},[185,1515,1516],{"class":187,"line":330},[185,1517,241],{"class":199},[10,1519,1520,1521,1524],{},"Jest jeszcze jeden układ, w którym to samo wraca: ktoś klika zgodę, ",[14,1522,1523],{},"zanim kontener zdąży się pobrać",". Mierzyłem taki przebieg — kliknięcie padło w 275 ms, kontener zaczął się ładować w 312 ms. Oba zdarzenia trafiają wtedy do kolejki jeszcze przed startem kontenera, a ten przetwarza je już ze zgodą udzieloną i może uruchomić tag dwukrotnie.",[10,1526,1527],{},"Mierzyłem ten przebieg, gdy tag stał jeszcze na „Raz na zdarzenie\", i dublowania nie zobaczyłem — ale zawdzięczałem to zabezpieczeniu wewnątrz samego Clarity, a nie własnej konfiguracji. Po przestawieniu na „Raz na stronę\" chroni już ustawienie, na które mam wpływ. Stąd to zalecenie jako wymóg, nie ozdoba.",[1328,1529,1531],{"id":1530},"poprawna-poprawka-zależy-od-trybu-zgody","Poprawna poprawka zależy od trybu zgody",[10,1533,1534],{},"Consent Mode ma dwa tryby i decyduje o nich jedno pole w konfiguracji tagu.",[10,1536,1537,1540],{},[14,1538,1539],{},"Tryb podstawowy",", czyli zaznaczone „Wymagaj dodatkowej zgody\": przed decyzją nie leci nic. Sprawdziłem to na drugim swoim serwisie, gdzie Analytics stał wtedy właśnie w tym trybie — zero żądań do Google, zero ciasteczek. To opisana wyżej sytuacja, w której drugi wyzwalacz jest konieczny.",[10,1542,1543,1546],{},[14,1544,1545],{},"Tryb zaawansowany",", czyli brak takiego wymogu: tag startuje zawsze, ale do czasu zgody wysyła sygnał bez ciasteczek i bez identyfikatorów.",[10,1548,1549,1550,1553],{},"Tu potrzebne jest zastrzeżenie, którego w poradnikach zwykle brakuje. Takie sygnały ",[14,1551,1552],{},"nie pojawiają się w raportach jako zwykłe odsłony",". Zasilają modelowanie zachowań, a modelowanie włącza się dopiero po przekroczeniu progów ruchu, których mały serwis nie osiągnie. Przy kilkudziesięciu wizytach miesięcznie różnica między trybami jest w praktyce mniejsza, niż wynikałoby z opisu.",[10,1555,1556,1557,1560],{},"Trzeba z tego wyciągnąć wniosek, który mnie samego kosztował chwilę: ",[14,1558,1559],{},"przy małym ruchu wybór trybu jest w praktyce decyzją głównie teoretyczną",". Ani jeden, ani drugi nie pokaże Ci w raportach wejść od osób, które paska nie dotknęły. Wybrałem zaawansowany, bo nic nie kosztuje technicznie, bo mój pasek i tak mówi użytkownikowi, że statystyki chodzą bez ciasteczek, i bo przy większym ruchu modelowanie zacznie działać bez przestawiania czegokolwiek. Gdyby serwis miał reklamy albo dane wrażliwe, wybrałbym odwrotnie.",[10,1562,1563,1564,1567],{},"Dochodzi do tego kwestia prawna. Argument, że sygnał przed zgodą niczego nie zapisuje na urządzeniu i nie niesie identyfikatora, jest ",[14,1565,1566],{},"moją interpretacją, a nie ustalonym stanowiskiem",". Wytyczne EDPB 2/2023 czytają artykuł 5 ustęp 3 dyrektywy ePrivacy szeroko i obejmują nim także piksele oraz śledzenie przez parametry adresu, czyli techniki bez zapisu po stronie przeglądarki. Jeśli doradzasz w tej sprawie klientowi, warto o tym wiedzieć, zanim powtórzysz moje uzasadnienie.",[10,1569,1570,1571,1574],{},"W trybie zaawansowanym drugi wyzwalacz wysyła ",[14,1572,1573],{},"drugie trafienie dla tej samej odsłony",", bo tag odpalił się już na zdarzeniu inicjującym. Dokładnie to zrobiłem, przenosząc rozwiązanie z jednego serwisu na drugi bez sprawdzenia, że tagi są tam skonfigurowane inaczej.",[10,1576,1577],{},"Czy to znaczy dwie odsłony w raportach, czy odzyskanie tej, która wcześniej poszła bez zgody i do raportów nie trafiła — tego z przeglądarki nie rozstrzygniesz. Widzisz dwa żądania, a co z nimi zrobi Google, zależy od obsługi trybu zgody po jego stronie. Dlatego w trybie zaawansowanym drugiego wyzwalacza nie dokładam: przy niepewności wolę jedno trafienie, o którym wiem, co znaczy.",[10,1579,1580],{},"Zasada, która z tego zostaje: najpierw wybierasz tryb, dopiero potem dobierasz wyzwalacze. W trybie podstawowym drugi wyzwalacz jest konieczny i jego skutek jest jednoznaczny. W zaawansowanym daj sobie z nim spokój.",[25,1582,1584],{"id":1583},"jak-to-sprawdzić-zamiast-zakładać","Jak to sprawdzić, zamiast zakładać",[10,1586,1587],{},"Panel Tag Managera pokazuje, co zamierzałeś. Do sprawdzenia, co naprawdę jest opublikowane, wystarczy pobrać kontener:",[176,1589,1593],{"className":1590,"code":1591,"language":1592,"meta":181,"style":181},"language-bash shiki shiki-themes one-dark-pro","curl -s \"https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX\" -o kontener.js\n","bash",[52,1594,1595],{"__ignoreMap":181},[185,1596,1597,1600,1603,1606,1609],{"class":187,"line":188},[185,1598,1599],{"class":195},"curl",[185,1601,1602],{"class":350}," -s",[185,1604,1605],{"class":279}," \"https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX\"",[185,1607,1608],{"class":350}," -o",[185,1610,1611],{"class":279}," kontener.js\n",[10,1613,1614],{},"W środku siedzi czytelny JSON. Lista tagów mówi, czego każdy wymaga i jak często może odpalić:",[176,1616,1620],{"className":1617,"code":1618,"language":1619,"meta":181,"style":181},"language-json shiki shiki-themes one-dark-pro","\"tags\":[{\"function\":\"__googtag\",\"consent\":[\"list\"],\n         \"once_per_event\":true,\"vtp_tagId\":\"G-XXXXXXXXXX\",\"tag_id\":3},\n        {\"function\":\"__cvt_MQDKZ\",\"consent\":[\"list\",\"analytics_storage\"],\n         \"once_per_load\":true,\"vtp_projectId\":\"xxxxxxxxxx\",\"tag_id\":6}]\n","json",[52,1621,1622,1654,1686,1713],{"__ignoreMap":181},[185,1623,1624,1627,1630,1633,1636,1639,1642,1645,1648,1651],{"class":187,"line":188},[185,1625,1626],{"class":279},"\"tags\"",[185,1628,1629],{"class":199},":[{",[185,1631,1632],{"class":294},"\"function\"",[185,1634,1635],{"class":199},":",[185,1637,1638],{"class":279},"\"__googtag\"",[185,1640,1641],{"class":199},",",[185,1643,1644],{"class":294},"\"consent\"",[185,1646,1647],{"class":199},":[",[185,1649,1650],{"class":279},"\"list\"",[185,1652,1653],{"class":199},"],\n",[185,1655,1656,1659,1661,1663,1665,1668,1670,1673,1675,1678,1680,1683],{"class":187,"line":217},[185,1657,1658],{"class":294},"         \"once_per_event\"",[185,1660,1635],{"class":199},[185,1662,1097],{"class":350},[185,1664,1641],{"class":199},[185,1666,1667],{"class":294},"\"vtp_tagId\"",[185,1669,1635],{"class":199},[185,1671,1672],{"class":279},"\"G-XXXXXXXXXX\"",[185,1674,1641],{"class":199},[185,1676,1677],{"class":294},"\"tag_id\"",[185,1679,1635],{"class":199},[185,1681,1682],{"class":350},"3",[185,1684,1685],{"class":199},"},\n",[185,1687,1688,1691,1693,1695,1698,1700,1702,1704,1706,1708,1711],{"class":187,"line":238},[185,1689,1690],{"class":199},"        {",[185,1692,1632],{"class":294},[185,1694,1635],{"class":199},[185,1696,1697],{"class":279},"\"__cvt_MQDKZ\"",[185,1699,1641],{"class":199},[185,1701,1644],{"class":294},[185,1703,1647],{"class":199},[185,1705,1650],{"class":279},[185,1707,1641],{"class":199},[185,1709,1710],{"class":279},"\"analytics_storage\"",[185,1712,1653],{"class":199},[185,1714,1715,1718,1720,1722,1724,1727,1729,1732,1734,1736,1738,1741],{"class":187,"line":244},[185,1716,1717],{"class":294},"         \"once_per_load\"",[185,1719,1635],{"class":199},[185,1721,1097],{"class":350},[185,1723,1641],{"class":199},[185,1725,1726],{"class":294},"\"vtp_projectId\"",[185,1728,1635],{"class":199},[185,1730,1731],{"class":279},"\"xxxxxxxxxx\"",[185,1733,1641],{"class":199},[185,1735,1677],{"class":294},[185,1737,1635],{"class":199},[185,1739,1740],{"class":350},"6",[185,1742,1743],{"class":199},"}]\n",[10,1745,1746,1747,1750],{},"To jest mój prawdziwy kontener, z zaślepionymi identyfikatorami. Indeks 0 to Google Analytics, indeks 1 to Clarity — ",[52,1748,1749],{},"__cvt_MQDKZ"," oznacza szablon z galerii, a nie wbudowany typ tagu.",[10,1752,1753,1754,1757,1758,1761,1762,1765,1766,1769,1770,1773],{},"Pole ",[52,1755,1756],{},"consent"," z wpisem ",[52,1759,1760],{},"analytics_storage"," znaczy tryb podstawowy, sama ",[52,1763,1764],{},"[\"list\"]"," — zaawansowany. ",[52,1767,1768],{},"once_per_event"," to „Raz na zdarzenie\", ",[52,1771,1772],{},"once_per_load"," to „Raz na stronę\". Widać więc od razu, że Analytics działa w trybie zaawansowanym, a Clarity w podstawowym.",[10,1775,1776],{},"Obok znajdziesz warunki wyzwalaczy i mapowanie jednych na drugie:",[176,1778,1780],{"className":1617,"code":1779,"language":1619,"meta":181,"style":181},"\"predicates\":[{\"function\":\"_eq\",\"arg0\":[\"macro\",0],\"arg1\":\"gtm.init\"},\n              {\"function\":\"_eq\",\"arg0\":[\"macro\",0],\"arg1\":\"gtm.js\"},\n              {\"function\":\"_eq\",\"arg0\":[\"macro\",0],\"arg1\":\"consent_granted\"}],\n\"rules\":[[[\"if\",0],[\"add\",0]],[[\"if\",1],[\"add\",1]],[[\"if\",2],[\"add\",1]]]\n",[52,1781,1782,1824,1858,1892],{"__ignoreMap":181},[185,1783,1784,1787,1789,1791,1793,1796,1798,1801,1803,1806,1808,1811,1814,1817,1819,1822],{"class":187,"line":188},[185,1785,1786],{"class":279},"\"predicates\"",[185,1788,1629],{"class":199},[185,1790,1632],{"class":294},[185,1792,1635],{"class":199},[185,1794,1795],{"class":279},"\"_eq\"",[185,1797,1641],{"class":199},[185,1799,1800],{"class":294},"\"arg0\"",[185,1802,1647],{"class":199},[185,1804,1805],{"class":279},"\"macro\"",[185,1807,1641],{"class":199},[185,1809,1810],{"class":350},"0",[185,1812,1813],{"class":199},"],",[185,1815,1816],{"class":294},"\"arg1\"",[185,1818,1635],{"class":199},[185,1820,1821],{"class":279},"\"gtm.init\"",[185,1823,1685],{"class":199},[185,1825,1826,1829,1831,1833,1835,1837,1839,1841,1843,1845,1847,1849,1851,1853,1856],{"class":187,"line":217},[185,1827,1828],{"class":199},"              {",[185,1830,1632],{"class":294},[185,1832,1635],{"class":199},[185,1834,1795],{"class":279},[185,1836,1641],{"class":199},[185,1838,1800],{"class":294},[185,1840,1647],{"class":199},[185,1842,1805],{"class":279},[185,1844,1641],{"class":199},[185,1846,1810],{"class":350},[185,1848,1813],{"class":199},[185,1850,1816],{"class":294},[185,1852,1635],{"class":199},[185,1854,1855],{"class":279},"\"gtm.js\"",[185,1857,1685],{"class":199},[185,1859,1860,1862,1864,1866,1868,1870,1872,1874,1876,1878,1880,1882,1884,1886,1889],{"class":187,"line":238},[185,1861,1828],{"class":199},[185,1863,1632],{"class":294},[185,1865,1635],{"class":199},[185,1867,1795],{"class":279},[185,1869,1641],{"class":199},[185,1871,1800],{"class":294},[185,1873,1647],{"class":199},[185,1875,1805],{"class":279},[185,1877,1641],{"class":199},[185,1879,1810],{"class":350},[185,1881,1813],{"class":199},[185,1883,1816],{"class":294},[185,1885,1635],{"class":199},[185,1887,1888],{"class":279},"\"consent_granted\"",[185,1890,1891],{"class":199},"}],\n",[185,1893,1894,1897,1900,1903,1905,1907,1910,1913,1915,1917,1920,1922,1924,1927,1929,1931,1933,1935,1937,1939,1941,1944,1946,1948,1950,1952],{"class":187,"line":244},[185,1895,1896],{"class":279},"\"rules\"",[185,1898,1899],{"class":199},":[[[",[185,1901,1902],{"class":279},"\"if\"",[185,1904,1641],{"class":199},[185,1906,1810],{"class":350},[185,1908,1909],{"class":199},"],[",[185,1911,1912],{"class":279},"\"add\"",[185,1914,1641],{"class":199},[185,1916,1810],{"class":350},[185,1918,1919],{"class":199},"]],[[",[185,1921,1902],{"class":279},[185,1923,1641],{"class":199},[185,1925,1926],{"class":350},"1",[185,1928,1909],{"class":199},[185,1930,1912],{"class":279},[185,1932,1641],{"class":199},[185,1934,1926],{"class":350},[185,1936,1919],{"class":199},[185,1938,1902],{"class":279},[185,1940,1641],{"class":199},[185,1942,1943],{"class":350},"2",[185,1945,1909],{"class":199},[185,1947,1912],{"class":279},[185,1949,1641],{"class":199},[185,1951,1926],{"class":350},[185,1953,1954],{"class":199},"]]]\n",[10,1956,1957,1958,1961],{},"Czyta się to tak: warunek 0 uruchamia tag 0, a warunki 1 i 2 uruchamiają tag 1. Czyli Analytics na zdarzeniu inicjującym, Clarity na odsłonie strony ",[14,1959,1960],{},"oraz"," na zdarzeniu zgody — dokładnie ten układ dwóch wyzwalaczy, o którym była mowa wyżej.",[10,1963,1964,1965,1968,1969,1975,1976,1979,1980,1982],{},"Jedna pułapka przy czytaniu: liczba w ",[52,1966,1967],{},"add"," to ",[14,1970,1971,1972],{},"indeks w tablicy ",[52,1973,1974],{},"tags",", a nie ",[52,1977,1978],{},"tag_id",". Obok stoją ",[52,1981,1978],{}," równe 3 i 6, więc łatwo je pomylić.",[10,1984,1985],{},"Nazwa zdarzenia jest widoczna wprost, więc od razu sprawdzisz, czy zgadza się co do znaku z tym, co wypycha strona.",[10,1987,1988,1989,1992,1993,1635],{},"Po stronie przeglądarki najszybszy pomiar daje parametr ",[52,1990,1991],{},"gcs"," w żądaniu do ",[52,1994,1995],{},"/g/collect",[176,1997,2001],{"className":1998,"code":1999,"language":2000,"meta":181,"style":181},"language-js shiki shiki-themes one-dark-pro","performance.getEntriesByType('resource')\n  .filter(z => /g\\/collect/.test(z.name))\n  .map(z => z.name.match(/[?&]gcs=([^&]*)/)?.[1]);\n","js",[52,2002,2003,2021,2063],{"__ignoreMap":181},[185,2004,2005,2008,2010,2013,2015,2018],{"class":187,"line":188},[185,2006,2007],{"class":203},"performance",[185,2009,70],{"class":199},[185,2011,2012],{"class":195},"getEntriesByType",[185,2014,229],{"class":199},[185,2016,2017],{"class":279},"'resource'",[185,2019,2020],{"class":199},")\n",[185,2022,2023,2026,2029,2031,2034,2037,2040,2043,2046,2048,2051,2053,2055,2057,2060],{"class":187,"line":217},[185,2024,2025],{"class":199},"  .",[185,2027,2028],{"class":195},"filter",[185,2030,229],{"class":199},[185,2032,2033],{"class":410},"z",[185,2035,2036],{"class":191}," =>",[185,2038,2039],{"class":294}," /g",[185,2041,2042],{"class":207},"\\/",[185,2044,2045],{"class":294},"collect/",[185,2047,70],{"class":199},[185,2049,2050],{"class":195},"test",[185,2052,229],{"class":199},[185,2054,2033],{"class":203},[185,2056,70],{"class":199},[185,2058,2059],{"class":294},"name",[185,2061,2062],{"class":199},"))\n",[185,2064,2065,2067,2070,2072,2074,2076,2079,2081,2083,2085,2088,2090,2093,2096,2099,2102,2105,2108,2111,2114,2116],{"class":187,"line":238},[185,2066,2025],{"class":199},[185,2068,2069],{"class":195},"map",[185,2071,229],{"class":199},[185,2073,2033],{"class":410},[185,2075,2036],{"class":191},[185,2077,2078],{"class":203}," z",[185,2080,70],{"class":199},[185,2082,2059],{"class":203},[185,2084,70],{"class":199},[185,2086,2087],{"class":195},"match",[185,2089,229],{"class":199},[185,2091,2092],{"class":294},"/",[185,2094,2095],{"class":350},"[?&]",[185,2097,2098],{"class":294},"gcs=(",[185,2100,2101],{"class":350},"[",[185,2103,2104],{"class":199},"^",[185,2106,2107],{"class":350},"&]*",[185,2109,2110],{"class":294},")/",[185,2112,2113],{"class":199},")?.[",[185,2115,1926],{"class":350},[185,2117,2118],{"class":199},"]);\n",[10,2120,2121,2124,2125,2128],{},[52,2122,2123],{},"G100"," oznacza oba magazyny odrzucone, ",[52,2126,2127],{},"G101"," — analitykę udzieloną. Liczba samych żądań mówi o dublowaniu.",[10,2130,2131],{},"Cztery stany warto przejść za każdym razem, na czystej sesji: przed decyzją, zaraz po kliknięciu, po przeładowaniu z zapisaną zgodą i po wycofaniu. Ten ostatni jest tym, o którym zapomniałem.",[25,2133,2135],{"id":2134},"kiedy-gotowa-platforma-jest-właściwym-wyborem","Kiedy gotowa platforma jest właściwym wyborem",[10,2137,2138],{},"Własny pasek zgód ma sens przy serwisie, który zbiera statystyki odwiedzin i nic ponadto. Gotowe rozwiązanie wygrywa w czterech sytuacjach:",[10,2140,2141,2144],{},[14,2142,2143],{},"AdSense, Ad Manager albo AdMob."," W Europejskim Obszarze Gospodarczym, Wielkiej Brytanii i Szwajcarii Google wymaga przy nich certyfikowanej platformy zgód obsługującej framework TCF od IAB. To setki stron specyfikacji i rejestr dostawców, który zmienia się co tydzień. Tego nie pisze się samodzielnie.",[10,2146,2147,2150],{},[14,2148,2149],{},"Obowiązek okresowego skanowania."," Platformy przeglądają serwis i wykrywają ciasteczka, które ktoś dołożył wtyczką bez informowania nikogo. Przy serwisie prowadzonym przez zespół to realna wartość.",[10,2152,2153,2156],{},[14,2154,2155],{},"Wiele języków i jurysdykcji."," Inny zakres zgód dla Unii, inny dla Kalifornii, inny dla Brazylii, do tego tłumaczenia.",[10,2158,2159,2162],{},[14,2160,2161],{},"Dowód zgodności na papierze."," Część klientów potrzebuje dokumentu z logo dostawcy do audytu. Własne 900 bajtów tego nie zastąpi, choćby działały lepiej.",[10,2164,2165],{},"W pozostałych przypadkach płacisz abonamentem i miejscem w kolejce ładowania za funkcje, których nie używasz.",[25,2167,2169],{"id":2168},"co-z-tego-zostaje","Co z tego zostaje",[10,2171,2172],{},"Skrypt zgód jest jedynym elementem strony, który z definicji musi wystartować przed wszystkim innym. Dlatego jego ocena po rozmiarze pliku prowadzi na manowce — liczy się, co przez niego czeka. Dziesięciokilobajtowy beacon kosztował mnie 450 ms FCP, a trzystukilobajtowa paczka skryptu nie kosztowała ani milisekundy tej samej metryki.",[10,2174,2175],{},"Własna implementacja daje kontrolę nad tym momentem. Kosztuje za to trzy pułapki opisane wyżej i obowiązek sprawdzenia w przeglądarce, czy to, co widzisz w panelu, faktycznie się dzieje.",[10,2177,2178],{},"Na trzy pułapki trafiłem sam, przy wdrożeniu. Kolejne trzy błędy — w tym jeden w opisie samej poprawki i jeden w kodzie wycofywania zgody — znalazł dopiero ktoś, kto przeczytał ten tekst uważniej ode mnie. Taki jest koszt rozwiązania, którego nikt za Ciebie nie utrzymuje.",[2180,2181,2182],"style",{},"html pre.shiki code .sVbv2, html code.shiki .sVbv2{--shiki-default:#61AFEF}html pre.shiki code .sVC51, html code.shiki .sVC51{--shiki-default:#D19A66}html pre.shiki code .subq3, html code.shiki .subq3{--shiki-default:#98C379}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sn6KH, html code.shiki .sn6KH{--shiki-default:#ABB2BF}html pre.shiki code .sVyAn, html code.shiki .sVyAn{--shiki-default:#E06C75}html pre.shiki code .sU0A5, html code.shiki .sU0A5{--shiki-default:#E5C07B}html pre.shiki code .s_ZVi, html code.shiki .s_ZVi{--shiki-default:#E06C75;--shiki-default-font-style:italic}html pre.shiki code .seHd6, html code.shiki .seHd6{--shiki-default:#C678DD}html pre.shiki code .sjrmR, html code.shiki .sjrmR{--shiki-default:#56B6C2}html pre.shiki code .sV9Aq, html code.shiki .sV9Aq{--shiki-default:#7F848E;--shiki-default-font-style:italic}",{"title":181,"searchDepth":217,"depth":217,"links":2184},[2185,2186,2187,2188,2189,2190,2191,2192,2197,2198,2199],{"id":27,"depth":217,"text":28},{"id":46,"depth":217,"text":47},{"id":88,"depth":217,"text":89},{"id":135,"depth":217,"text":136},{"id":173,"depth":217,"text":174},{"id":510,"depth":217,"text":511},{"id":762,"depth":217,"text":763},{"id":1311,"depth":217,"text":1312,"children":2193},[2194,2195,2196],{"id":1330,"depth":238,"text":1331},{"id":1400,"depth":238,"text":1401},{"id":1530,"depth":238,"text":1531},{"id":1583,"depth":217,"text":1584},{"id":2134,"depth":217,"text":2135},{"id":2168,"depth":217,"text":2169},"2026-09-23","Logika zgód z Consent Mode v2 zmieściła się u mnie w 900 bajtach po kompresji. Sam skrypt wejściowy gotowej platformy to 28 kB i osobne połączenie do obcej domeny. Co trzeba w takim pasku zrobić dobrze, na czym się przewróciłem i kiedy gotowiec jest jednak właściwym wyborem.","md","Wydajność",{},"/img/blog/pasek-zgod-na-wlasnym-kodzie.webp","Pięć ciastek na otwartym laptopie — cztery ułożone w stos i jedno obok, na gładziku","/pasek-zgod-na-wlasnym-kodzie",{"title":5,"description":2201},{"loc":2207},"pasek-zgod-na-wlasnym-kodzie",false,null,"B2K0-fVNkYYd9kHwioHDN-es13BP5DBEeewfO9_oPTk",1790159852163]