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.

SEO & Performance

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

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

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

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.

SEO & Performance

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

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

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

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.

SEO & Performance

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

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

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

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
  • Automatyzacja Procesów AI
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
MarketingNext.jsReact

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.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
18 października 2025 09:09
Czytanie
6 min czytania
Aktualizacja
26 maja 2026 10:37

Integracja Google Analytics 4 w Next.js App Router to coś więcej niż wklejenie snippetu w <head>, ponieważ nawigacja client-side, React Server Components, Consent Mode v2 i wybór między ręcznym gtag.js, a @next/third-parties sprawiają, że łatwo wprowadzić analitykę pozornie poprawną, ale w praktyce generującą zdublowane page views albo niepełne dane - nawiasem mówiąc, obie te sytuacje zdarzyły mi się w przeszłości. Ten artykuł łączy setup GA4 z realnymi niuansami App Router i podstawami planowania zdarzeń.

Artykuł w skrócie

  • Dwa podejścia: gtag.js vs @next/third-parties — @next/third-parties daje prostszy setup i lepsze Core Web Vitals; ręczne gtag daje pełną kontrolę nad zdarzeniami
  • Consent Mode v2 jest obowiązkowy — dla EU ustaw domyślne wartości denied przed załadowaniem skryptu gtag; dopiero potem ładuj tagi
  • App Router i double page views — wyłącz send_page_view: false i wysyłaj page view ręcznie w useEffect reagującym na zmiany pathname
  • dataLayer przed gtag.js — inicjalizuj window.dataLayer = window.dataLayer || [] przed załadowaniem skryptu, żeby eventy nie przepadły
  • Testuj w DebugView GA4 — po wdrożeniu zawsze weryfikuj eventy w GA4 → DebugView zanim przejdziesz na produkcję
  • Jeden typowany moduł eventów — trzymaj własne eventy GA4 w jednym miejscu; gdy dochodzi więcej tagów lub marketing potrzebuje niezależności od deployów, rozważ GTM

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ę.

Uwaga

Najpierw ustaw Consent Mode, dopiero potem ładuj tagi. Jeśli zgody startują za późno, pomiar może wyglądać poprawnie lokalnie, ale dane reklamowe i analityczne będą niespójne w produkcji.

Zanim zaczniesz

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: Manualne z gtag.js

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

App Router (Next.js 13+)

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 Core Web Vitals to zestaw metryk Google oceniających realne doświadczenie użytkownika: LCP (szybkość ładowania), INP (responsywność) i CLS (stabilność wizualna). Wpływają na ranking i konwersję.. 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

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

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 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: @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

Code
npm install @next/third-parties

Podstawowa konfiguracja

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

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.

Consent Mode — implementacja

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

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

DebugView

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 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

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

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.

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.

Werdykt Labu

Konfiguracja GA4 w Next.js nie jest tak czarną magią, jak może się początkowo wydawać, ale wymaga zrozumienia kilku specyficznych rzeczy, client-side routing, kolejność ładowania skryptów, Consent Mode. Ręczna instalacja daje Ci najwięcej kontroli, a pakiet @next/third-parties jest szybszy, ale mniej elastyczny.

Najważniejsze to pamiętać o trzech sprawach:

  • Poprawne śledzenie odsłon strony przy przechodzeniu między podstronami.
  • Wdrożenie Consent Mode dla użytkowników z Unii Europejskiej.
  • Zawsze testuj swoje ustawienia za pomocą "DebugView", zanim wrzucisz je na działającą stronę.
  • Zanim zaczniesz1 min
  • Podejście 1: Manualne z gtag.js2 min
  • Podejście 2: @next/third-parties1 min
  • Consent Mode — implementacja1 min
  • Warunki ładowania skryptu1 min
  • Testowanie i debugowanie1 min
  • Typowe błędy1 min
  • Które podejście wybrać?1 min
  • Werdykt Labu1 min

Często zadawane pytania

Źródła i dokumentacjaZweryfikowano: 26 maja 2026

Materiały wykorzystane do weryfikacji artykułu „Google Analytics 4 w Next.js App Router — konfiguracja z gtag i @next/third-parties”:

Next.js Third Party Libraries, GA4: Confirm that you're collecting data, GA4: Mark events as key events, Set up consent mode on websites, Troubleshoot consent mode with Tag Assistant.

Seria

Analityka i kampanie w Next.js
Część 1 / 4
  1. Google Analytics 4 w Next.js App Router — konfiguracja z gtag i @next/third-parties
  2. 2GA4 Data API w Next.js – budujemy własny dashboard analityczny
  3. 3Google Ads Remarketing w React – dynamiczne listy odbiorców i personalizacja reklam
  4. 4Landing page dla Google Ads w Next.js — jak budować strony, które konwertują
Maciej Sala

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 rozwija własne projekty side-projecty.

Moje artykułyWięcej o mnie

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

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Google Tag Manager w Next.js — dataLayer, custom triggers i debugowanie jak pro
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.

Maciej Sala

Maciej Sala

Founder Strivelab

25 września 2025
GA4 Data API w Next.js – budujemy własny dashboard analityczny
GA4 Data API w Next.js – budujemy własny dashboard analityczny

GA4 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
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
Poprzedni 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
Następny wpisE2E testy w Next.js App Router – kompletny setup Cypress + CI/CDCypress E2E w Next.js App Router krok po kroku. Konfiguracja, fixtures, custom commands, CI i wzorce, które ograniczają flaky testy.
Maciej Sala

Maciej Sala

Founder Strivelab

31 października 2025