StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

RealizacjeO mnieBlog
Porozmawiajmy
PL
EN

Nowoczesne strony internetowe dla firm, które myślą odważnie.

Przewiń do góry

Nazwa

StriveLab Maciej Sala

NIP

6772218995

REGON

524008527

E-mail

contact@strivelab.pl

Usługi główne
  • Tworzenie stron internetowych
  • Strony internetowe Next.js
  • Strony internetowe Astro
  • Strony internetowe React
Inne usługi
  • Usługi
  • SEO & Performance Sprint
  • QA & Stabilizacja
  • Konsultacje Product / Delivery
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
AnalitykaNext.jsReact

Google Tag Manager w Next.js — dataLayer, custom triggers i debugowanie jak pro

Jak wdrożyć Google Tag Manager w Next.js App Router bez chaosu w dataLayer: page_view, custom events, ecommerce, consent mode i debugowanie.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
25 września 2025 12:45
Czytanie
8 min czytania
Aktualizacja
13 maja 2026 08:14

W skrócie

  • GTM w Next.js ma sens wtedy, gdy potrzebujesz wielu tagów, remarketingu, częstych zmian bez deploya albo współpracy z marketingiem.
  • Najważniejszy element wdrożenia to nie sam snippet, tylko stabilny kontrakt dataLayer.
  • W App Router wysyłaj własny page_view po zmianie route, zamiast liczyć na klasyczny page reload.
  • Consent ustawiaj przed startem kontenera albo trzymaj całość w sprawdzonym CMP/GTM Consent API.
  • Każdy event testuj w GTM Preview, Tag Assistant i w Network/Console, zanim ktoś oznaczy go jako konwersję.
  • Granica odpowiedzialności — kod odpowiada za jakość danych w dataLayer, GTM za dystrybucję tych danych do narzędzi marketingowych.

Google Tag Manager (GTM, czyli Google Tag Manager, pozwala zarządzać tagami i skryptami marketingowymi bez każdej zmiany w kodzie aplikacji.) potrafi bardzo szybko uporządkować analitykę, ale równie szybko potrafi zamienić ją w czarną skrzynkę. W klasycznej stronie problem jest jeszcze do opanowania: dokument się przeładowuje, tagi odpalają się od nowa, a marketer widzi coś, co przypomina znany model page view. W aplikacji Next.js z App Routerem sytuacja jest inna, bo nawigacja działa jak w SPA, czyli Single Page Application, działa bez pełnego przeładowania dokumentu przy każdej zmianie widoku.. Adres URL się zmienia, komponenty się przełączają, ale kontener GTM nie startuje od zera.

Dlatego dobre wdrożenie GTM w Next.js nie polega na wklejeniu snippetu i nadziei, że „Google sobie poradzi”. Polega na zbudowaniu jasnego kontraktu między aplikacją a kontenerem: developer wysyła spójne eventy do dataLayer, a marketer buduje na nich tagi, triggery i konwersje bez grzebania w kodzie.

Kiedy GTM ma sens, a kiedy tylko komplikuje projekt

Jeżeli strona używa wyłącznie GA4 i ma trzy podstawowe eventy, GTM nie zawsze jest najlepszym wyborem. Bezpośredni Google tag albo @next/third-parties/google może być prostszy: mniej warstw, mniej miejsc do pomyłki, łatwiejszy code review.

GTM zaczyna wygrywać, gdy dochodzą kolejne potrzeby:

  • Google Ads conversion tracking i remarketing,
  • Meta Pixel, LinkedIn Insight Tag, TikTok Pixel lub inne platformy reklamowe,
  • testy A/B, narzędzia heatmapowe i skrypty marketing automation,
  • wiele eventów konwersji zarządzanych przez marketing,
  • potrzeba szybkiej zmiany tagów bez deploya aplikacji.

Wtedy GTM jest warstwą operacyjną dla marketingu, a dataLayer staje się API między produktem a analityką. To API musi być stabilne, nazwane i udokumentowane. Bez tego marketer będzie budował triggery na klasach CSS, tekście przycisku albo strukturze DOM, która zmieni się przy następnym refactorze.

Instalacja GTM w Next.js App Router

Next.js udostępnia komponent GoogleTagManager w @next/third-parties/google. Oficjalnie można go dodać w root layout i przekazać gtmId. To dobry punkt startowy, szczególnie gdy chcesz użyć wspieranego przez Next.js sposobu ładowania third-party scripts.

app/layout.tsx
Code
import { GoogleTagManager } from '@next/third-parties/google'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="pl">
      <body>{children}</body>
      <GoogleTagManager gtmId="GTM-XXXXXXX" />
    </html>
  )
}

W bardziej kontrolowanych wdrożeniach nadal często wybieram własny komponent oparty o next/script. Powód jest praktyczny: łatwiej wymusić kolejność consent defaults, warunki ładowania, środowiska i fallback noscript.

components/GTM.tsx
Code
'use client'
 
import Script from 'next/script'
 
const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID
 
export function GTMHead() {
  if (!GTM_ID || process.env.NODE_ENV !== 'production') return null
 
  return (
    <Script id="gtm-script" strategy="afterInteractive">
      {`
        (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
        new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
        j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
        'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
        })(window,document,'script','dataLayer','${GTM_ID}');
      `}
    </Script>
  )
}
 
export function GTMNoScript() {
  if (!GTM_ID || process.env.NODE_ENV !== 'production') return null
 
  return (
    <noscript>
      <iframe
        src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
        height="0"
        width="0"
        style={{ display: 'none', visibility: 'hidden' }}
      />
    </noscript>
  )
}
app/layout.tsx
Code
import { GTMHead, GTMNoScript } from '@/components/GTM'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="pl">
      <body>
        <GTMNoScript />
        {children}
        <GTMHead />
      </body>
    </html>
  )
}

Nie ładuj samego kontenera GTM przez beforeInteractive, jeśli nie masz bardzo konkretnego powodu. Consent defaults to inna sprawa: one faktycznie powinny pojawić się przed tagami, które zależą od zgody. Sam kontener zwykle wystarczy uruchomić po interaktywności strony.

DataLayer jako kontrakt, nie worek na losowe dane

dataLayer to tablica obiektów JavaScript. Google Tag Manager czyta kolejne wpisy, przetwarza je po kolei i odpala tagi, których triggery pasują do danego eventu. Najważniejszy detal: jeśli pushujesz dane bez event, GTM może mieć problem z przewidywalnym odpaleniem tagów we właściwym momencie. Dlatego event powinien być jawny.

Zacznij od małego wrappera. Nie pushuj obiektów bezpośrednio z każdego komponentu, bo po kilku miesiącach nikt nie będzie wiedział, kto wysyła form_submit, kto formSubmit, a kto lead_sent.

lib/dataLayer.ts
Code
type DataLayerEvent = Record<string, unknown> & {
  event: string
}
 
declare global {
  interface Window {
    dataLayer?: Array<Record<string, unknown>>
    gtag?: (...args: unknown[]) => void
  }
}
 
export function pushToDataLayer(data: DataLayerEvent) {
  if (typeof window === 'undefined') return
 
  window.dataLayer = window.dataLayer || []
  window.dataLayer.push(data)
}
 
export function pushEcommerceEvent(
  event: string,
  ecommerce: Record<string, unknown>,
) {
  if (typeof window === 'undefined') return
 
  window.dataLayer = window.dataLayer || []
  window.dataLayer.push({ ecommerce: null })
  window.dataLayer.push({ event, ecommerce })
}

W dużym projekcie ten plik szybko warto rozbić na moduły: analytics/pageView, analytics/ecommerce, analytics/forms, analytics/consent. Ważne, żeby komponent UI nie znał szczegółów struktury ecommerce ani nazw parametrów używanych przez GA4.

Page view w App Router

Najczęstszy błąd w Next.js polega na tym, że kontener GTM ładuje się raz, a zespół zakłada, że page views będą zliczane jak w klasycznej stronie. Nie będą. App Router zmienia widok bez pełnego przeładowania dokumentu, więc potrzebujesz własnego eventu na zmianę pathname i searchParams.

components/GTMPageView.tsx
Code
'use client'
 
import { Suspense, useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'
import { pushToDataLayer } from '@/lib/dataLayer'
 
function GTMPageViewInner() {
  const pathname = usePathname()
  const searchParams = useSearchParams()
 
  useEffect(() => {
    const query = searchParams?.toString()
    const pagePath = query ? `${pathname}?${query}` : pathname
 
    pushToDataLayer({
      event: 'page_view',
      page_path: pagePath,
      page_location: window.location.href,
      page_title: document.title,
    })
  }, [pathname, searchParams])
 
  return null
}
 
export function GTMPageView() {
  return (
    <Suspense fallback={null}>
      <GTMPageViewInner />
    </Suspense>
  )
}

W GTM tworzysz potem Custom Event Trigger dla page_view i podpinasz pod niego tag eventowy GA4, czyli Google Analytics 4, to aktualna wersja platformy analitycznej Google do pomiaru zdarzeń i zachowań użytkowników.. Jeśli używasz Google tag jako konfiguracji bazowej, wyłącz automatyczne wysyłanie page view tam, gdzie grozi duplikat. Jedno źródło prawdy jest ważniejsze niż „więcej danych”.

Ecommerce: mniej kreatywności, więcej standardu GA4

Przy ecommerce nie wymyślaj własnych nazw, jeśli nie musisz. GA4 ma rekomendowane eventy i strukturę items[], więc używaj view_item, add_to_cart, begin_checkout, purchase oraz standardowych parametrów. Dzięki temu raporty e-commerce, import konwersji do Google Ads i diagnostyka będą dużo prostsze.

lib/dataLayer-ecommerce.ts
Code
import { pushEcommerceEvent } from './dataLayer'
 
type Product = {
  id: string
  name: string
  brand?: string
  category?: string
  price: number
}
 
type CartItem = {
  product: Product
  quantity: number
}
 
type Order = {
  id: string
  totalValue: number
  tax?: number
  shippingCost?: number
  couponCode?: string
  items: CartItem[]
}
 
function toItem(product: Product, index?: number) {
  return {
    item_id: product.id,
    item_name: product.name,
    item_brand: product.brand,
    item_category: product.category,
    price: product.price / 100,
    quantity: 1,
    index,
  }
}
 
export const dlEcommerce = {
  viewItemList: (listName: string, products: Product[]) =>
    pushEcommerceEvent('view_item_list', {
      item_list_id: listName,
      item_list_name: listName,
      items: products.map((product, index) => toItem(product, index)),
    }),
 
  viewItem: (product: Product) =>
    pushEcommerceEvent('view_item', {
      currency: 'PLN',
      value: product.price / 100,
      items: [toItem(product)],
    }),
 
  addToCart: (product: Product, quantity = 1) =>
    pushEcommerceEvent('add_to_cart', {
      currency: 'PLN',
      value: (product.price * quantity) / 100,
      items: [{ ...toItem(product), quantity }],
    }),
 
  beginCheckout: (items: CartItem[], total: number) =>
    pushEcommerceEvent('begin_checkout', {
      currency: 'PLN',
      value: total / 100,
      items: items.map((item) => ({
        ...toItem(item.product),
        quantity: item.quantity,
      })),
    }),
 
  purchase: (order: Order) =>
    pushEcommerceEvent('purchase', {
      transaction_id: order.id,
      currency: 'PLN',
      value: order.totalValue / 100,
      tax: (order.tax ?? 0) / 100,
      shipping: (order.shippingCost ?? 0) / 100,
      coupon: order.couponCode,
      items: order.items.map((item) => ({
        ...toItem(item.product),
        quantity: item.quantity,
      })),
    }),
}

Najważniejsze są dwie zasady. Po pierwsze: wartości pieniężne powinny być liczbami w walucie raportowania, nie stringami typu "149,99 zł". Po drugie: purchase musi mieć stabilny transaction_id, bo bez niego trudno diagnozować duplikaty i import konwersji do Google Ads.

Custom events dla marketingu

Nie każdy event jest ecommerce. W stronach usługowych, SaaS-ach i aplikacjach B2B ważniejsze będą formularze, kliknięcia CTA, przejścia między krokami onboardingowymi, pobrania plików i interakcje z kalkulatorem.

lib/dataLayer-custom.ts
Code
import { pushToDataLayer } from './dataLayer'
 
export const dlCustom = {
  ctaClick: (ctaName: string, location: string) =>
    pushToDataLayer({
      event: 'cta_click',
      cta_name: ctaName,
      cta_location: location,
    }),
 
  formStart: (formName: string) =>
    pushToDataLayer({
      event: 'form_start',
      form_name: formName,
    }),
 
  formSubmit: (formName: string, success: boolean) =>
    pushToDataLayer({
      event: 'form_submit',
      form_name: formName,
      form_success: success,
    }),
 
  fileDownload: (fileName: string, fileUrl: string) =>
    pushToDataLayer({
      event: 'file_download',
      file_name: fileName,
      file_url: fileUrl,
    }),
 
  signUp: (method: string) =>
    pushToDataLayer({
      event: 'sign_up',
      signup_method: method,
    }),
}

Nazwy eventów powinny być stabilne i nudne. cta_click jest lepsze niż heroBigGreenButtonClicked, bo może obsłużyć wiele miejsc w produkcie, a szczegóły przenosisz do parametrów. To samo dotyczy formularzy: jeden form_submit z form_name i form_success daje lepszy model danych niż piętnaście osobnych eventów.

Dokumentacja dataLayer dla marketera

Dobry dataLayer bez dokumentacji nadal jest słaby. Marketer musi wiedzieć, jakie eventy istnieją, kiedy się odpalają i jakie parametry może mapować w GTM. Najprostszy format to tabela w Notion, Google Docs albo markdown w repo.

docs/data-layer-contract.md
Code
## page_view
 
Kiedy: każda zmiana route w App Router oraz hard reload.
Parametry: page_path, page_title, page_location.
Typowy trigger w GTM: Custom Event = page_view.
 
## cta_click
 
Kiedy: kliknięcie przycisku CTA mierzonego marketingowo.
Parametry: cta_name, cta_location.
Typowy trigger w GTM: Custom Event = cta_click.
 
## form_submit
 
Kiedy: próba wysłania formularza.
Parametry: form_name, form_success.
Typowy trigger w GTM: Custom Event = form_submit + warunek form_success = true.
 
## purchase
 
Kiedy: potwierdzone zamówienie po stronie aplikacji.
Parametry: ecommerce.transaction_id, ecommerce.value, ecommerce.currency, ecommerce.items[].
Typowy trigger w GTM: Custom Event = purchase.

Taki dokument zmienia rozmowę. Zamiast „czy możesz dodać event na ten przycisk?” pojawia się pytanie „czy ten przycisk powinien użyć istniejącego cta_click, czy potrzebujemy nowego typu zdarzenia?”. To jest różnica między analityką utrzymywalną a przypadkowym zbiorem tagów.

Konfiguracja tagów w GTM

Po stronie GTM marketer zwykle tworzy kilka warstw.

Google tag / GA4 config inicjalizuje pomiar. Jeśli page view wysyłasz ręcznie, wyłącz automatyczny page view tam, gdzie powodowałby duplikaty. Ten tag nie powinien być odpowiedzialny za mierzenie każdej nawigacji w App Routerze.

GA4 Event Tag: page_view powinien odpalać się na Custom Event page_view i mapować parametry page_path, page_title, page_location.

GA4 Event Tag: form_submit może odpalać się na Custom Event form_submit, ale często warto dodać warunek form_success equals true, żeby nie raportować walidacyjnych porażek jako leadów.

Google Ads Conversion Tag zwykle podpinasz pod event biznesowy, np. purchase albo generate_lead. Wartość konwersji mapujesz z Data Layer Variable, a transaction_id przekazujesz tam, gdzie narzędzie to obsługuje.

Remarketing i piksele zewnętrzne powinny mieć jasno opisane warunki odpalenia i consent requirements. To nie są „niewinne skrypty”; wpływają na prywatność, performance i jakość danych.

Consent Mode: kolejność ma znaczenie

Consent to nie jest popup z przyciskiem „Akceptuję”. Dla tagów ważne jest to, jaki stan zgody obowiązuje w momencie odpalenia eventu. Dlatego defaulty powinny być ustawione zanim tagi zależne od consent zaczną działać.

Jeśli obsługujesz banner w aplikacji, możesz ustawić domyślne zgody przed kontenerem GTM:

components/ConsentDefaults.tsx
Code
import Script from 'next/script'
 
export function ConsentDefaults() {
  return (
    <Script id="consent-defaults" strategy="beforeInteractive">
      {`
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('consent', 'default', {
          analytics_storage: 'denied',
          ad_storage: 'denied',
          ad_user_data: 'denied',
          ad_personalization: 'denied',
          wait_for_update: 500
        });
      `}
    </Script>
  )
}

Po decyzji użytkownika wysyłasz update:

lib/consent.ts
Code
import { pushToDataLayer } from './dataLayer'
 
export function grantMarketingConsent() {
  window.gtag?.('consent', 'update', {
    analytics_storage: 'granted',
    ad_storage: 'granted',
    ad_user_data: 'granted',
    ad_personalization: 'granted',
  })
 
  pushToDataLayer({
    event: 'consent_update',
    consent_analytics: 'granted',
    consent_ads: 'granted',
  })
}

Jeśli consent jest zarządzany przez CMP w GTM, nie doklejaj własnych Custom HTML tagów z przypadkowym gtag('consent'). Oficjalne zalecenie Google jest proste: do consent w Tag Managerze używaj Consent APIs i odpowiednich szablonów, bo stan zgody musi być przetworzony przed eventami, które zależą od tej zgody.

Debugowanie GTM w Next.js

Najlepsze debugowanie zaczyna się od GTM Preview. Uruchamiasz Preview, wpisujesz URL, przechodzisz przez scenariusz i sprawdzasz:

  • czy event trafił do dataLayer,
  • czy trigger pasuje do eventu,
  • czy tag się odpalił,
  • jakie wartości miały Data Layer Variables,
  • czy consent pozwalał na odpalenie taga.

Drugim narzędziem jest Tag Assistant. Szczególnie przy consent mode pokazuje, czy zgody są ustawiane w poprawnej kolejności i czy tagi nie odpalają się zbyt wcześnie.

Do szybkiej diagnostyki w konsoli przydają się proste komendy:

Code
// Wszystkie dotychczasowe wpisy
console.table(window.dataLayer)
 
// Tylko konkretne eventy
window.dataLayer.filter((entry) => entry.event === 'purchase')
 
// Tymczasowy podgląd nowych pushy
const originalPush = window.dataLayer.push.bind(window.dataLayer)
 
window.dataLayer.push = function (...args) {
  console.log('dataLayer push:', args)
  return originalPush(...args)
}

W development uważaj na React Strict Mode. Jeśli widzisz podwójne eventy lokalnie, najpierw sprawdź produkcyjny build albo preview deployment. Dopiero jeśli duplikaty występują poza dev mode, szukaj błędu w efektach Reacta, automatycznym page view w GA4 albo podwójnie opublikowanych tagach w GTM.

Najczęstsze błędy

Duplikaty page_view zwykle biorą się z jednoczesnego automatycznego page view w Google tagu i ręcznego eventu w App Routerze. Wybierz jeden model i trzymaj się go konsekwentnie.

Brak ecommerce: null powoduje mieszanie danych ecommerce między eventami. To klasyczny problem przy view_item, add_to_cart i purchase.

Triggery oparte na CSS selectorach są kruche. Jeśli marketer podpina konwersję pod .btn-primary:nth-child(2), to refactor UI może zepsuć pomiar bez żadnego błędu w buildzie.

Custom HTML bez review to ryzyko wydajności, bezpieczeństwa i błędów w SPA. Wbudowane szablony tagów są bezpieczniejsze niż dowolny skrypt wklejony do kontenera.

Brak procesu publikacji kończy się tym, że nikt nie wie, która wersja GTM zmieniła dane. Korzystaj z workspace, opisuj zmiany i testuj Preview przed publikacją.

Współpraca developer - marketer

Najzdrowszy model jest prosty:

  1. Developer tworzy i utrzymuje dataLayer contract.
  2. Marketer zgłasza potrzeby pomiarowe językiem biznesowym: lead, zakup, kliknięcie CTA, pobranie pliku.
  3. Developer mapuje te potrzeby na eventy i parametry.
  4. Marketer konfiguruje tagi w GTM.
  5. Obie strony sprawdzają scenariusz w Preview mode przed publikacją.

Ten proces może wyglądać wolniej niż „wrzućmy tag od razu”, ale w praktyce oszczędza czas. Mniej duplikatów, mniej niejasnych konwersji, mniej sytuacji, w których kampania optymalizuje się na błędny event.

Podsumowanie

GTM w Next.js jest dobrym narzędziem, jeśli traktujesz go jak warstwę zarządzania tagami, a nie jak miejsce do naprawiania braków w aplikacji. Aplikacja powinna wysyłać czytelne, stabilne eventy. GTM powinien je odbierać, mapować do narzędzi marketingowych i respektować consent.

Najważniejsze decyzje są nudne: jedna konwencja nazw, jeden sposób mierzenia page view, jawne eventy dla ecommerce i formularzy, dokument dataLayer oraz regularne testy w Preview mode. To właśnie te nudne decyzje sprawiają, że po pół roku nadal rozumiesz własną analitykę.

Źródła i dokumentacja

  • Next.js: Third Party Libraries
  • Next.js: using Google Tag Manager with @next/third-parties
  • Google Tag Manager: The data layer
  • Google Tag Platform: dataLayer developer guide
  • Google Tag Platform: Set up consent mode on websites
  • Google Tag Platform: Troubleshoot consent mode with Tag Assistant

Często zadawane pytania

Pracuję z tym zawodowo.

Jeśli chcesz połączyć SEO, analitykę, Google Ads i warstwę techniczną strony w jeden sensowny system wzrostu, skontaktuj się ze mną. Pomagam układać wdrożenia, które nie kończą się na samym tagowaniu, ale wspierają widoczność, pomiar i konwersję.

Skontaktuj się ze mną
Maciej Sala

O autorze

Maciej Sala

Maciej Sala — project manager i frontendowiec z doświadczeniem w marketingu internetowym. Na co dzień pracuję z Reactem, Next.js i TypeScriptem, łącząc perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat związany z branżą gier wideo jako project manager i game designer.

Absolwent historii na Uniwersytecie Jagiellońskim i studiów podyplomowych z marketingu internetowego na Akademii Górniczo-Hutniczej w Krakowie. Poza pracą trenuje na siłowni, maluje figurki i realizuje własne projekty.

Moje artykułyWięcej o mnie
Poprzedni wpisGA4 Data API w Next.js – budujemy własny dashboard analitycznyGA4 Data API w Next.js bez skrótów myślowych: service account, cache, limity, bezpieczeństwo i budowa własnego dashboardu na danych z Analytics.
Maciej Sala

Maciej Sala

Founder Strivelab

31 sierpnia 2025
Następny wpisCypress Component Testing w React i Next.js — kiedy naprawdę ma sensCypress Component Testing w React i Next.js bez marketingowej mgły. Kiedy daje przewagę nad RTL, jak go skonfigurować i gdzie kończą się jego możliwości.
Maciej Sala

Maciej Sala

Founder Strivelab

6 października 2025
  • Kiedy GTM ma sens, a kiedy tylko komplikuje projekt1 min
  • Instalacja GTM w Next.js App Router1 min
  • DataLayer jako kontrakt, nie worek na losowe dane1 min
  • Page view w App Router1 min
  • Ecommerce: mniej kreatywności, więcej standardu GA41 min
  • Custom events dla marketingu1 min
  • Dokumentacja dataLayer dla marketera1 min
  • Konfiguracja tagów w GTM1 min
  • Consent Mode: kolejność ma znaczenie1 min
  • Debugowanie GTM w Next.js1 min
  • Najczęstsze błędy1 min
  • Współpraca developer - marketer1 min
  • Podsumowanie1 min
  • Źródła i dokumentacja1 min

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Google Analytics 4 w Next.js App Router — konfiguracja z gtag i @next/third-parties
Google Analytics 4 w Next.js App Router — konfiguracja z gtag i @next/third-parties

Jak poprawnie wdrożyć GA4 w Next.js App Router: gtag, @next/third-parties, page_view przy client-side navigation, consent mode v2 i custom events bez chaosu w danych.

Maciej Sala

Maciej Sala

Founder Strivelab

18 października 2025
Google Ads Remarketing w React – dynamiczne listy odbiorców i personalizacja reklam
Google Ads Remarketing w React – dynamiczne listy odbiorców i personalizacja reklam

Remarketing Google Ads w React i Next.js bez marketingowych uproszczeń: eventy, Merchant Center, listy odbiorców w GA4, Customer Match i wymogi consent.

Maciej Sala

Maciej Sala

Founder Strivelab

6 grudnia 2025
Pagespeed 100/100 w Next.js — case study optymalizacji strony usługowej
Pagespeed 100/100 w Next.js — case study optymalizacji strony usługowej

Jak osiągnąć wynik 100/100 w PageSpeed Insights dla strony Next.js? Case study optymalizacji LCP, INP i CLS — fonty, obrazy, JavaScript, third-party scripts i Server Components.

Maciej Sala

Maciej Sala

Founder Strivelab

10 kwietnia 2026