Przejdź do treści

Konfiguracja Google Analytics 4 z paczką next third parties

Uniknij gubienia sesji i odsłon przy nawigacji po stronie klienta. Zobacz, jak wdrożyć GA4 w App Routerze przez gtag, @next/third-parties albo Google Tag Manager.

Maciej Sala

Founder StriveLab

7 min czytaniaAktualizacja

Najważniejsze na początek jest to, że @next/third-parties to wygodne i stabilne rozwiązanie, natomiast przy bardziej restrykcyjnym consent mode, warunkowym ładowaniu skryptu albo niestandardowej logice związanej z wysyłaniem pageview, ręczny setup nadal daje nam znacznie większą kontrolę.

Zanim zaczniesz konfigurację GA4 w Next.js

Potrzebujesz Measurement ID z panelu GA4. Znajdziesz go w Admin → Data Streams → wybierz stream → Measurement ID. Format to G-XXXXXXXXXX.

Trzymaj to ID w zmiennej środowiskowej:

Code
# .env.local
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX

Prefiks NEXT_PUBLIC_ jest kluczowy — bez niego zmienna nie będzie dostępna w kodzie klienckim.

Podejście 1: manualna konfiguracja GA4 przez gtag.js

To klasyczne podejście, które daje pełną kontrolę nad tym, kiedy i jak ładowany jest skrypt GA4.

GA4 w App Routerze Next.js

Utwórz komponent GoogleAnalytics i osadź go w layout.tsx:

Code
// src/components/GoogleAnalytics.tsx
'use client'
 
import Script from 'next/script'
import { usePathname, useSearchParams } from 'next/navigation'
import { useEffect } from 'react'
 
const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID
 
export default function GoogleAnalytics() {
  const pathname = usePathname()
  const searchParams = useSearchParams()
 
  useEffect(() => {
    if (!GA_MEASUREMENT_ID || typeof window.gtag !== 'function') return
 
    const url =
      pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : '')
 
    window.gtag('event', 'page_view', {
      page_path: url,
      page_location: window.location.href,
      page_title: document.title,
    })
  }, [pathname, searchParams])
 
  if (!GA_MEASUREMENT_ID) return null
 
  return (
    <>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
        strategy="afterInteractive"
      />
      <Script
        id="google-analytics"
        strategy="afterInteractive"
        dangerouslySetInnerHTML={{
          __html: `
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '${GA_MEASUREMENT_ID}', {
              page_path: window.location.pathname,
              send_page_view: false
            });
          `,
        }}
      />
    </>
  )
}
Code
// src/app/layout.tsx
import { Suspense } from 'react'
import GoogleAnalytics from '@/components/GoogleAnalytics'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="pl">
      <body>
        <Suspense fallback={null}>
          <GoogleAnalytics />
        </Suspense>
        {children}
      </body>
    </html>
  )
}

Kilka istotnych detali w tym kodzie. send_page_view: false w konfiguracji gtag wyłącza automatyczne pageviews, zamiast tego wysyłamy je ręcznie w useEffect, reagując na zmiany pathname i searchParams. Bez tego przy client-side navigation (kliknięcie w Link) GA4 nie zarejestruje przejścia między stronami. strategy="afterInteractive" ładuje skrypt po hydratacji strony, co minimalizuje wpływ na . Suspense wokół komponentu jest potrzebny, bo useSearchParams wymaga granicy Suspense w App Router.

Diagram
Bezpieczna kolejność ładowania GA4 w Next.js App Router

Typowanie TypeScript dla gtag

Dodaj typ dla gtag na obiekcie window:

Code
// src/types/gtag.d.ts
interface Window {
  gtag: (...args: [string, ...unknown[]]) => void
  dataLayer: Array<unknown>
}

Helper do wysyłania eventów GA4

Stwórz prosty helper, żeby nie powtarzać logiki w każdym komponencie:

Code
// src/lib/analytics.ts
export const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID
 
export function trackEvent(
  eventName: string,
  parameters?: Record<string, string | number | boolean>,
) {
  if (!GA_MEASUREMENT_ID) return
  window.gtag('event', eventName, parameters)
}
 
// Użycie w komponencie:
// trackEvent('sign_up', { method: 'email' });
// trackEvent('add_to_cart', { item_id: 'SKU_001', value: 49.99, currency: 'PLN' });

Eventy GA4 i key events: co mierzyć?

GA4 opiera pomiar na zdarzeniach. Najpierw korzystaj ze zdarzeń zbieranych automatycznie lub przez Enhanced Measurement, a dla działań biznesowych stosuj rekomendowane nazwy, takie jak generate_lead, sign_up, add_to_cart, begin_checkout i purchase. Dzięki temu raporty są łatwiejsze do interpretacji niż przy własnej nazwie dla każdej odmiany kliknięcia.

Zdarzenie ważne dla wyniku biznesowego oznacz w GA4 jako key event. Przykładowo strona usługowa może oznaczyć generate_lead, a sklep purchase; nie oznaczaj jako key event każdej drobnej interakcji. W panelu Analytics robisz to w Admin -> Events, wskazując istniejące zdarzenie albo definiując nowe z góry.

Podejście 2: GA4 przez @next/third-parties

Pakiet @next/third-parties to oficjalne rozwiązanie od Vercel, które upraszcza integrację z popularnymi skryptami third-party. Warto jednak śledzić aktualną dokumentację przy większych wdrożeniach.

Instalacja @next/third-parties

Code
npm install @next/third-parties

Podstawowa konfiguracja GA4

Code
// src/app/layout.tsx
import { GoogleAnalytics } from '@next/third-parties/google'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="pl">
      <body>
        {children}
        <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID!} />
      </body>
    </html>
  )
}

To wszystko — komponent automatycznie ładuje skrypt gtag i konfiguruje go z Twoim Measurement ID.

Wysyłanie eventów przez @next/third-parties

Pakiet eksportuje helper sendGAEvent:

Code
'use client'
 
import { sendGAEvent } from '@next/third-parties/google'
 
export function SignUpButton() {
  return (
    <button
      onClick={() => {
        sendGAEvent('event', 'sign_up', { method: 'email' })
      }}
    >
      Zarejestruj się
    </button>
  )
}

Ograniczenia @next/third-parties

@next/third-parties jest wygodny, ale ma swoje ograniczenia. Nie daje takiej kontroli nad momentem inicjalizacji Google tag, jak ręczny setup, co jest problematyczne, jeśli potrzebujesz bardziej rygorystycznego consent mode. Do tego łatwo dojść do momentu, w którym i tak trzeba dopisać własną logikę do warunkowego ładowania, page_view albo dodatkowych integracji.

Dla stron w UE Consent Mode to konieczność. Oto jak go zaimplementować z manualnym podejściem:

Code
// src/components/GoogleAnalytics.tsx
'use client'
 
import Script from 'next/script'
 
const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID
 
export default function GoogleAnalytics() {
  if (!GA_MEASUREMENT_ID) return null
 
  return (
    <>
      {/* Consent defaults MUST load before gtag */}
      <Script
        id="consent-defaults"
        strategy="beforeInteractive"
        dangerouslySetInnerHTML={{
          __html: `
            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',
              functionality_storage: 'granted',
              security_storage: 'granted',
              wait_for_update: 500
            });
          `,
        }}
      />
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
        strategy="afterInteractive"
      />
      <Script
        id="google-analytics-config"
        strategy="afterInteractive"
        dangerouslySetInnerHTML={{
          __html: `
            gtag('js', new Date());
            gtag('config', '${GA_MEASUREMENT_ID}');
          `,
        }}
      />
    </>
  )
}

Następnie, kiedy użytkownik zaakceptuje cookies w Twoim banerze:

Code
// src/lib/consent.ts
export function grantAnalyticsConsent() {
  window.gtag('consent', 'update', {
    analytics_storage: 'granted',
  })
}
 
export function grantAllConsent() {
  window.gtag('consent', 'update', {
    analytics_storage: 'granted',
    ad_storage: 'granted',
    ad_user_data: 'granted',
    ad_personalization: 'granted',
  })
}

Kluczową rzeczą jest to, że najpierw musisz ustawić domyślne zgody (np. wszystko zablokowane), zanim załaduje się główny kod GA4. Dopiero potem, gdy użytkownik wyrazi zgodę, aktualizujesz te ustawienia. Robiąc to w złej kolejności, GA4 może zacząć zbierać dane, zanim użytkownik się zgodzi, co jest niezgodne z obowiązującym prawem.

Warunki ładowania skryptu GA4

Jeśli wolisz, żeby kod GA4 w ogóle się nie ładował, dopóki użytkownik nie wyrazi zgody, możesz to zrobić tak:

Code
'use client'
 
import Script from 'next/script'
import { useEffect, useState } from 'react'
 
export default function GoogleAnalytics() {
  const [hasConsent, setHasConsent] = useState(false)
 
  useEffect(() => {
    const consent = localStorage.getItem('analytics-consent')
    if (consent === 'granted') {
      setHasConsent(true)
    }
 
    // Nasłuchuj na zmiany zgody z cookie bannera
    const handler = () => setHasConsent(true)
    window.addEventListener('analytics-consent-granted', handler)
    return () =>
      window.removeEventListener('analytics-consent-granted', handler)
  }, [])
 
  if (!hasConsent) return null
 
  return (
    <>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}`}
        strategy="afterInteractive"
      />
      {/* ... config script */}
    </>
  )
}

Testowanie i debugowanie GA4 w Next.js

DebugView w Google Analytics 4

Włącz specjalny tryb debug, dodając parametr w konfiguracji:

Code
gtag('config', 'G-XXXXXXXXXX', {
  debug_mode: true,
})

Albo automatycznie w trybie deweloperskim:

Code
gtag('config', GA_MEASUREMENT_ID, {
  debug_mode: process.env.NODE_ENV === 'development',
})

Po włączeniu powyższego trybu, wszystkie zdarzenia, które wysyłasz, pojawią się w czasie rzeczywistym w sekcji "DebugView" w panelu GA4.

Sprawdzanie GA4 w DevTools

Otwórz narzędzia deweloperskie (zazwyczaj F12), przejdź do zakładki "Network" i wpisz "collect" w polu wyszukiwania - zobaczysz tam wszystkie zapytania wysyłane do serwerów Google Analytics. Właśnie w ten sposób, szybko możemy sprawdzić, czy zdarzenia w ogóle są wysyłane.

Tag Assistant dla GA4

Google Tag Assistant (rozszerzenie Chrome albo wersja webowa na tagassistant.google.com) pozwala podejrzeć, jakie eventy są wysyłane i czy konfiguracja jest poprawna.

Typowe błędy konfiguracji GA4 w Next.js

Brak pageviews przy client-side navigation to najczęstszy problem. Dzieje się tak ponieważ Next.js przy nawigacji przez <Link> nie ładuje strony od nowa, więc gtag nie wysyła automatycznie pageview. Rozwiązaniem jest ręczne wysyłanie page_view w useEffect reagującym na zmiany pathname.

Podwójne pageviews — jeśli masz ustawione send_page_view: true (domyślne) i jednocześnie wysyłasz ręcznie pageviews w useEffect, dostajesz duplikaty. By to naprawić, ustaw w konfiguracji send_page_view: false.

Brak danych w development — wtyczki blokujące reklamy i rozszerzenia prywatności blokują requesty do google-analytics.com. W trakcie developmentu testuj w oknie incognito z wyłączonymi rozszerzeniami albo korzystaj z DebugView.

Zmienne środowiskowe undefined — zapomniane NEXT_PUBLIC_ prefiks lub brak .env.local w katalogu projektu. Pamiętaj, że po dodaniu zmiennej musisz zrestartować serwer deweloperski.

Google Tag Manager: kiedy jeden tag przestaje wystarczać

Bezpośredni Google tag albo @next/third-parties wystarczy, dopóki mierzysz samo GA4 i kilka zdarzeń. GTM zaczyna wygrywać, gdy dochodzą konwersje Google Ads, piksele Meta, LinkedIna czy TikToka, testy A/B, narzędzia heatmapowe i potrzeba zmiany tagów bez deployu aplikacji. Kontener staje się wtedy warstwą operacyjną marketingu, a dataLayer zamienia się w API między produktem a analityką, które musi być stabilne, nazwane i udokumentowane. Bez takiego kontraktu marketer zbuduje triggery na klasach CSS i tekstach przycisków, a pierwszy refactor interfejsu po cichu wyłączy pomiar.

Kontener dodasz komponentem GoogleTagManager z @next/third-parties/google w głównym layoucie. Gdy potrzebujesz pełnej kontroli nad kolejnością zgód, środowiskami i warunkami ładowania, lepiej sprawdza się własny komponent na next/script. Niezależnie od sposobu instalacji zdarzenia wysyłaj przez jeden wrapper, a nie bezpośrednio z komponentów, bo po kilku miesiącach nikt nie będzie wiedział, kto wysyła form_submit, a kto lead_sent:

lib/dataLayer.ts
Code
type DataLayerEvent = Record<string, unknown> & { event: string }
 
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 || []
  // Czyści poprzedni obiekt, żeby dane produktów nie przeszły między zdarzeniami
  window.dataLayer.push({ ecommerce: null })
  window.dataLayer.push({ event, ecommerce })
}

Najwięcej problemów w App Routerze sprawia page view. Nawigacja nie przeładowuje dokumentu, więc wybierz jeden model pomiaru i trzymaj się go konsekwentnie. Możesz zostawić automatyczne odsłony na podstawie historii przeglądarki w Google tagu albo wysyłać własne zdarzenie page_view z komponentu nasłuchującego usePathname() i podpiąć je w GTM pod Custom Event Trigger. Oba mechanizmy naraz dają podwójne odsłony.

Przy zgodach liczy się kolejność. Domyślne stany Consent Mode ustaw, zanim kontener odpali pierwszy tag zależny od zgody, a jeśli zgodami zarządza CMP w GTM, korzystaj z jego szablonu i Consent APIs zamiast własnych tagów Custom HTML z gtag('consent'). Przed publikacją każdej wersji kontenera przejdź scenariusz w trybie Preview i sprawdź, czy zdarzenie trafiło do dataLayer, czy trigger pasuje i czy tag odpalił się przy właściwym stanie zgody. Podwójne zdarzenia widoczne tylko lokalnie to zwykle efekt React Strict Mode, więc zanim zaczniesz szukać błędu, sprawdź build produkcyjny.

gtag.js czy @next/third-parties: które podejście wybrać?

Wybór @next/third-parties ma większy sens, jeśli chcesz szybko dodać GA4 bez szerokiej customizacji, a Twoja strona nie wymaga złożonego warunkowego ładowania skryptu.

Podejście manualne z gtag sprawdzi się lepiej, gdy potrzebujesz pełnej kontroli nad Consent Mode (strony w UE, więc też PL), chcesz warunkowo ładować skrypt na podstawie zgody użytkownika, potrzebujesz niestandardowej logiki śledzenia pageviews lub integrujesz GA4 z innymi narzędziami (np. Google Tag Manager).

Dla większości polskich projektów — gdzie RODO wymaga Consent Mode — manualne podejście daje więcej kontroli i jest bezpieczniejszym wyborem - właśnie to rekomenduje. Kiedy pojawia się potrzeba remarketingu, wielu tagów i niezależności marketingu od deployów, następnym krokiem jest Google Tag Manager wdrożony według zasad z poprzedniej sekcji.

Kampanie, landing page, tracking konwersji, GA4 i GTM w jednym procesie.
Google Ads i Analityka

Często zadawane pytania

Czy @next/third-parties wystarczy do GA4?

Wystarczy w prostych wdrożeniach, ale przy Consent Mode, niestandardowych eventach i pełnej kontroli page_view często lepszy jest ręczny setup gtag lub warstwa GTM.

Dlaczego page_view w Next.js trzeba obsłużyć ostrożnie?

App Router używa nawigacji client-side, więc samo załadowanie skryptu nie wystarcza do poprawnego pomiaru kolejnych przejść między podstronami.

Kiedy zamiast gtag wybrać Google Tag Manager?

GTM ma sens, gdy marketing potrzebuje samodzielnie zarządzać tagami, triggerami i narzędziami bez deployu aplikacji przy każdej zmianie.

Dlaczego GA4 nie rejestruje przejść między stronami w Next.js?

Next.js używa client-side navigation — przy kliknięciu w <Link> strona nie jest przeładowywana, więc gtag nie wysyła automatycznie pageview. Rozwiązaniem jest ustawienie send_page_view: false w konfiguracji gtag i ręczne wysyłanie eventu page_view w useEffect, który reaguje na zmiany pathname i searchParams z hooków next/navigation.

Czy `@next/third-parties` jest gotowe do użycia produkcyjnego?

Pakiet działa stabilnie i jest oficjalnie rozwijany przez Vercel. Do prostego wdrożenia GA4 bez specjalnych wymagań może być wystarczający, ale jeśli potrzebujesz precyzyjnej kontroli nad Consent Mode, warunkowego ładowania skryptu na podstawie zgody lub niestandardowej logiki pageviews, lepiej wybrać manualne podejście z gtag.

Jak poprawnie wdrożyć Consent Mode v2 w Next.js?

Consent Mode wymaga ustawienia domyślnych wartości (denied) przed załadowaniem skryptu gtag. W Next.js oznacza to użycie strategy="beforeInteractive" dla skryptu z domyślnymi zgodami i strategy="afterInteractive" dla samego gtag. Po wyrażeniu zgody przez użytkownika wywołujesz gtag('consent', 'update', {...}) z wartościami granted. Kolejność ładowania jest krytyczna — odwrócenie jej powoduje zbieranie danych przed wyrażeniem zgody.

Gdzie znajdę Measurement ID dla GA4?

Measurement ID znajdziesz w panelu Google Analytics w sekcji Admin → Data Streams → kliknij na wybrany stream → Measurement ID. Format to G-XXXXXXXXXX, wklej to ID do zmiennej środowiskowej NEXT_PUBLIC_GA_MEASUREMENT_ID w pliku .env.local — prefiks NEXT_PUBLIC_ jest obowiązkowy, bez niego zmienna nie będzie dostępna w kodzie klienckim.

Dlaczego widzę zduplikowane pageviews w GA4?

Duplikaty pageviews wynikają najczęściej z jednoczesnego aktywnego ustawienia send_page_view: true (domyślne) w konfiguracji gtag i ręcznego wysyłania pageview w useEffect. Rozwiązaniem jest ustawienie send_page_view: false w konfiguracji gtag, żeby wyłączyć automatyczne pageviews i polegać wyłącznie na ręcznym wysyłaniu.

Czy GA4 działa poprawnie w React Server Components?

Tak, ale sam tag GA4 i każde odwołanie do window, document albo gtag() musi żyć po stronie klienta. Server Components mogą bez problemu renderować HTML strony, natomiast tracking osadzasz w Client Componentach lub helperach wywoływanych z klienta.

Jak testować GA4 w środowisku lokalnym?

Włącz tryb debugowania w konfiguracji GA4 (debug_mode: true), a zdarzenia pojawią się w "DebugView" w panelu GA4. Alternatywnie sprawdź zakładkę Network w DevTools i filtruj po „collect", a zobaczysz requesty do google-analytics.com/g/collect. Pamiętaj, że wtyczki blokujące reklamy mogą to utrudniać, więc testuj w trybie incognito z wyłączonymi rozszerzeniami.

Czy warto używać GA4 z GTM zamiast bezpośredniej integracji?

To zależy od potrzeb, ponieważ bezpośrednia integracja gtag jest prostsza i daje pełną kontrolę deweloperowi. GTM warto rozważyć, gdy masz kilka narzędzi analitycznych i marketingowych, zespół marketingowy potrzebuje samodzielnie dodawać tagi bez deployów, lub gdy chcesz centralnie zarządzać zgodami dla wielu tagów. Zasady wdrożenia GTM w App Routerze opisuję w osobnej sekcji tego artykułu.

O autorze

Maciej Sala

Maciej Sala — konsultant technologiczny produktów cyfrowych i web developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną
LH.pl – Hosting Mango

Biblioteka wiedzy na temat Next.js

Czytaj dalej

Zobacz więcej wpisów
Google Ads Conversion Tracking w Next.js

Musisz mieć świadomość, że jak wydajesz pieniądze na Google Ads, a nie mierzysz konwersji poprawnie, algorytm ma mniej sygnałów do optymalizacji stawek i kampanii. To zwykle oznacza gorszą jakość danych, słabsze uczenie modelu i trudniejsze decyzje po stronie marketingu.

Maciej Sala

Maciej Sala

Founder StriveLab

Landing page pod Google Ads w Next.js i wysoka konwersja

Landing page może ograniczać skuteczność kampanii mimo trafnej reklamy i poprawnie dobranych zapytań. Strona docelowa musi spójnie łączyć obietnicę reklamy, wygodne wykonanie działania, poprawny pomiar oraz informację o jakości pozyskanych leadów. W tym przewodniku pokazuję, jak zbudować te elementy w Next.js i uniknąć błędów zniekształcających dane Google Ads.

Maciej Sala

Maciej Sala

Founder StriveLab

Jak osiągnąć 100/100 w Lighthouse Performance w Next.js? Case study

Mamy tu typową stronę usługową zbudowaną w Next.js, wyposażoną w Google Analytics, Google Tag Manager , prosty formularz kontaktowy, galerię zdjęć i animacje. Na początku testy PageSpeed Insights pokazały wyniki: 67/100 na urządzeniach mobilnych i 89/100 na komputerach stacjonarnych.

Maciej Sala

Maciej Sala

Founder StriveLab

LH.pl – Cloud Server 1C4G