Przejdź do treści

Google Analytics 4 w Next.js App Router — konfiguracja z gtag i @next/third-parties

GA4 w Next.js App Router bez dziurawej atrybucji — gtag, consent mode v2 i zdarzenia przy client-side navigation. Jak to zrobić poprawnie raz.

Maciej Sala

Founder StriveLab

6 min czytaniaOpublikowano 18 października 2025 (Aktualizacja 26 maja 2026)

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.

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 GTM — jak go wdrożyć poprawnie w App Router opisuję w artykule o Google Tag Manager w Next.js.

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. Szczegóły tej decyzji opisuję w artykule o GTM w Next.js.

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend 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 rozwijam własne projekty.

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

Skontaktuj się ze mną

Biblioteka wiedzy na temat Next.js

Czytaj dalej

Zobacz więcej wpisów
GA4 Data API w Next.js – budujemy własny dashboard analityczny

Jak zbudować custom dashboard analityczny w Next.js, który pobiera dane bezpośrednio z GA4 Data API? Server-side, autoryzacja, cache i wizualizacja z Recharts.

Maciej Sala

Maciej Sala

Founder StriveLab

Google Ads Remarketing w React – dynamiczne listy odbiorców i personalizacja reklam

Jak wdrożyć remarketing Google Ads w aplikacji React? Dynamiczny remarketing ecommerce, listy odbiorców na podstawie zachowań użytkowników i integracja z listami odbiorców w GA4.

Maciej Sala

Maciej Sala

Founder StriveLab

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

Praktyczny poradnik GTM w Next.js: dataLayer contract, SPA page views, custom triggers, ecommerce, consent mode i współpraca deweloper–marketer.

Maciej Sala

Maciej Sala

Founder StriveLab