GA4, czyli Google Analytics 4, to aktualna wersja platformy analitycznej Google do pomiaru zdarzeń i zachowań użytkowników. to narzędzie zaprojektowane dla analityków, a nie dla klientów — interfejs jest gęsty, terminologia mocno techniczna, a dostęp wymaga konta Google z uprawnieniami do właściwości. Dla większości klientów to bariera zbyt wysoka, żeby samodzielnie z niego korzystać.
Custom dashboard w Next.js rozwiązuje kilka konkretnych problemów. Klient widzi wybrane metryki w kontekście swojego biznesu, bez konieczności logowania do GA4 i rozumienia, czym jest "sessionMedium". Możesz połączyć dane GA4 z innymi źródłami — zamówieniami z CMS-a, leadami z CRM-a, przychodami z własnej bazy — i pokazać je razem w jednym widoku. Możesz również zbudować publiczny lub wewnętrzny panel z wbudowaną analityką, który jest integralną częścią produktu, a nie zewnętrznym narzędziem.
Warto jednak wiedzieć, kiedy to podejście nie ma sensu: jeśli potrzebujesz pełnej elastyczności eksploracji danych, zaawansowanych segmentów i porównań, GA4 (lub Looker Studio z konektorem GA4) będzie szybsze i tańsze w utrzymaniu. Custom dashboard opłaca się wtedy, gdy wiesz z góry, jakie metryki chcesz pokazywać i komu.
Krok 1: Autoryzacja – Service Account
GA4 Data API wymaga autoryzacji OAuth2. Dla aplikacji server-side właściwym wyborem jest Service Account — konto techniczne bez hasła, identyfikowane kluczem JSON. Nie używa interaktywnego logowania, nie wymaga odświeżania tokenów przez użytkownika i daje się bezpiecznie trzymać w zmiennych środowiskowych serwera.
Alternatywą jest OAuth2 z tokenami użytkownika, ale to ma sens tylko wtedy, gdy chcesz, żeby każdy użytkownik widział dane ze swojej własnej właściwości GA4. Przy dashboardzie dla jednej właściwości Service Account jest prostszy i bezpieczniejszy.
Ważna kwestia uprawnień: Service Account potrzebuje roli Viewer na poziomie właściwości GA4 — nie na poziomie konta Google Analytics ani projektu Google Cloud. Dodajesz go w GA4: Admin → Property Access Management. Rola Viewer wystarczy do wszystkich operacji odczytu. Nie nadawaj Editor ani Admin — zasada minimalnych uprawnień obowiązuje tak samo jak przy każdym innym kluczu API.
Utworzenie Service Account
Google Cloud Console → APIs & Services → Credentials,
Create Credentials → Service Account,
Pobierz klucz JSON,
W GA4: Admin → Property Access Management → dodaj email Service Account z rolą Viewer.
Environment variables
Klucz JSON z Google Cloud zawiera kilkanaście pól, ale do autoryzacji potrzebujesz tylko dwóch: client_email i private_key. Reszta (project_id, token_uri itp.) jest obsługiwana przez bibliotekę automatycznie.
Zwróć uwagę na private_key — w pliku JSON Google zapisuje znaki nowej linii jako \n (literalnie backslash-n). W zmiennej środowiskowej też je tak trzymamy, a w kodzie zamieniamy na prawdziwe znaki nowej linii przez .replace(/\\n/g, '\n'). Bez tej zamiany autoryzacja się wysypie z enigmatycznym błędem invalid_grant.
Instalacja
Code
npm install @google-analytics/data
Krok 2: GA4 Client – server-side
BetaAnalyticsDataClient to główna klasa z pakietu @google-analytics/data i pomimo nazwy z "Beta" jest to stabilna, produkcyjna wersja klienta - Google po prostu nie zmienił nazwy po wyjściu z bety. Tworzymy go jako singleton, żeby nie inicjalizować połączenia i autoryzacji przy każdym requescie — to ważne szczególnie w Server Components, które mogą być wywoływane wielokrotnie w trakcie jednego page load.
Code
// lib/ga4/client.tsimport { BetaAnalyticsDataClient } from '@google-analytics/data'let client: BetaAnalyticsDataClient | null = nullexport function getGA4Client(): BetaAnalyticsDataClient { if (!client) { client = new BetaAnalyticsDataClient({ credentials: { client_email: process.env.GA4_CLIENT_EMAIL, private_key: process.env.GA4_PRIVATE_KEY?.replace(/\\n/g, '\n'), }, }) } return client}export const GA4_PROPERTY_ID = process.env.GA4_PROPERTY_ID!
Krok 3: Funkcje do pobierania danych
GA4 Data API operuje na trzech pojęciach: dimensions (Wymiar w GA4 to atrybut opisujący dane, np. pagePath, sessionSource, deviceCategory — odpowiada na pytanie 'czego dotyczy ten rekord'. — co segmentujesz, np. pagePath, sessionSource, date), metrics (Metryka w GA4 to wartość liczbowa, np. totalUsers, sessions, bounceRate — odpowiada na pytanie 'ile'. — co mierzysz, np. totalUsers, sessions, bounceRate) i dateRanges (zakres dat). Każde zapytanie to kombinacja tych trzech elementów.
Ważna gotcha: nie wszystkie kombinacje wymiarów i metryk są kompatybilne. GA4 zwróci błąd INCOMPATIBLE_DIMENSIONS_METRICS, jeśli zestawisz wymiary i metryki, których Google nie może obliczyć razem. Przed budową produkcyjnych zapytań warto je przetestować w GA4 Dimensions & Metrics Explorer.
Daty w GA4 Data API mają dwa formaty. Możesz podawać "30daysAgo", "7daysAgo", "yesterday", "today" — wygodne dla dashboardów z presetami — albo konkretne daty "2026-01-01" w formacie YYYY-MM-DD. W odpowiedzi GA4 zwraca daty jako "20260101" (bez separatorów) — stąd helper formatGA4Date poniżej.
GA4 Data API ma limity i kwoty tokenowe, więc cache nie jest tu optymalizacją "na potem", tylko częścią poprawnej architektury od pierwszego dnia.
Jak działają limity GA4 Data API? Google używa modelu tokenowego: każde zapytanie kosztuje określoną liczbę tokenów z puli przypisanej do właściwości. Prosta kwerenda kosztuje mniej niż złożona z wieloma wymiarami, dużym limit i długim zakresem dat. Pula odnawia się w ciągu doby, ale jeśli ją wyczerpiesz (np. dashboard bez cache'u odwiedzony przez wielu użytkowników jednocześnie), API zwraca 429 Resource Exhausted. Bez cache'u jeden popularny dashboard może wyczerpać dzienny limit właściwości w ciągu minut.
Funkcja eksportowana z pliku route.ts w App Routerze, obsługująca metodę HTTP (GET, POST itd.). Przyjmuje standardowy Request i zwraca Response — to webowy odpowiednik dawnych API Routes. vs Server Component to komponent renderowany wyłącznie na serwerze. Nie trafia do przeglądarki jako kod JavaScript — wysyła gotowy HTML. Dlatego jego console.log pojawia się w terminalu serwera, a nie w konsoli przeglądarki, i nie widać go w React DevTools. z revalidate — to dwie różne strategie cachowania, które warto rozumieć:
Route Handler (/api/analytics/...) ma sens, gdy chcesz dynamicznie zmieniać zakres dat po stronie klienta (Date Range Picker) i pobierać dane bez przeładowania strony. Cache ustawiasz przez nagłówki HTTP.
Server Component z export const revalidate = 300 jest prostszy, gdy zakres dat jest stały lub ustawiany przez URL params. Next.js sam zarządza cachowaniem — dane są pobierane raz przy pierwszym requescie i odświeżane co 5 minut w tle przez ISR, czyli Incremental Static Regeneration, pozwala odświeżać strony statyczne w tle bez pełnego rebuildu — strona jest serwowana z cache, a Next.js regeneruje ją po upływie czasu revalidate..
Code
// app/api/analytics/overview/route.tsimport { NextRequest, NextResponse } from 'next/server'import { getOverview } from '@/lib/ga4/queries'export async function GET(request: NextRequest) { const { searchParams } = request.nextUrl const startDate = searchParams.get('startDate') ?? '30daysAgo' const endDate = searchParams.get('endDate') ?? 'today' try { const data = await getOverview(startDate, endDate) return NextResponse.json(data, { headers: { // Cache na 5 minut – dane analityczne nie muszą być real-time 'Cache-Control': 's-maxage=300, stale-while-revalidate=600', }, }) } catch (error) { console.error('GA4 API error:', error) return NextResponse.json( { error: 'Failed to fetch analytics' }, { status: 500 }, ) }}
Recharts to biblioteka oparta na SVG, czyli Scalable Vector Graphics, to format grafiki wektorowej renderowanej przez przeglądarkę — skalowalny bez utraty jakości, dostępny w DOM. i D3, zbudowana jako natywne komponenty React. Jej największa zaleta w kontekście Next.js to, że działa wyłącznie po stronie klienta — i to jest jednocześnie jedyne ograniczenie, o którym warto pamiętać.
W App Router oznacza to: wykresy muszą żyć w Client Components ("use client"). Dane pobierasz server-side (Server Component lub API Route), a do komponentu przekazujesz je przez props jako zwykłe tablice obiektów. To podział, który w przykładzie poniżej realizuje para DashboardPage (server) + DashboardClient (client).
ResponsiveContainer opakowuje każdy wykres i sprawia, że dopasowuje się do szerokości kontenera — bez tego Recharts renderuje wykres o sztywnych wymiarach, który się nie skaluje na ekranach mobilnych.
useSearchParams() w Next.js App Router wymaga opakowania komponentu w <Suspense>, ponieważ inaczej Next.js wymusi dynamiczne renderowanie całej strony nadrzędnej lub rzuci błąd podczas buildu. Granica Suspense izoluje dynamiczną część (Date Range Picker czytający URL) od reszty strony, która może pozostać statyczna i być serwowana z cache.
Porównanie z poprzednim okresem to jedna z najczęstszych potrzeb na dashboardach klienckich. Chodzi o znalezienie prostej odpowiedzi: "czy to lepiej czy gorzej niż miesiąc temu?". Da się to zrealizować dwoma osobnymi requestami, ale GA4 Data API obsługuje tablicę dateRanges z dwoma zakresami w jednym zapytaniu - co jest bardziej optymalne. Jest i tańsze tokenowo, i wygodniejsze w implementacji.
Kluczowym detalem jest, że przy wielu dateRanges dodajemy wymiar dateRange, bo to on pozwala później rozróżnić, który wiersz należy do okresu current, a który do previous.
Monitorowanie limitów – PropertyQuota
Każda odpowiedź z GA4 Data API zawiera pole propertyQuota z informacją o bieżącym zużyciu tokenów. Większość tutoriali to pomija — a to jeden z bardziej praktycznych mechanizmów, jeśli budujesz dashboard produkcyjny.
Pole returnPropertyQuota: true w każdym zapytaniu daje aktualny stan limitów po wykonaniu tej kwerendy. Możesz logować te wartości (np. do konsoli serwera albo własnego systemu monitoringu) i reagować zanim GA4 zwróci 429. Przykładowo: jeśli tokensPerDay.remaining spada poniżej 20%, możesz zwiększyć TTL cache'a dynamicznie albo wysłać alerta.
Graceful degradation – co gdy GA4 jest niedostępny?
GA4 Data API zdarza się być chwilowo niedostępne lub wolne. Bez obsługi błędów cały dashboard pada z 500. Dobra architektura zakłada, że dane analityczne to funkcja wspierająca, nie krytyczna — i odpowiednio obsługuje awarię.
Wzorzec jest prosty, ponieważ każda funkcja zwraca { data, error } zamiast rzucać wyjątek. W Server Component można wtedy pokazać UI z informacją o niedostępności danych zamiast białej strony:
Promise.all z safe* funkcjami zapewnia, że błąd jednego endpointa GA4 nie blokuje renderowania pozostałych części dashboardu. Użytkownik widzi to, co jest dostępne, z jasnym komunikatem o problemie.
Bezpieczeństwo i rate limits
Nigdy nie eksponuj Service Account credentials na frontendzie. Wszystkie zapytania do GA4 Data API muszą iść przez server-side (API Routes, Server Components, Server Actions).
Rate limits: GA4 Data API używa kwot tokenowych zależnych od typu zapytań i właściwości. Cache jest częścią poprawnej architektury — bez niego dashboard z kilkoma widżetami i kilkudziesięcioma równoczesnymi użytkownikami wyczerpie dobowy limit szybciej, niż się spodziewasz.
Autoryzacja dashboardu: Zabezpiecz /dashboardMiddleware w Next.js to funkcja wykonywana przed obsługą żądania — pozwala np. sprawdzić sesję i przekierować na login zanim strona zostanie wyrenderowana. lub server-side auth check. Dane z GA4 — nawet agregowane — mogą ujawniać wrażliwe informacje o ruchu i przychodach.
Code
// middleware.tsimport { NextResponse } from 'next/server'import type { NextRequest } from 'next/server'export function middleware(request: NextRequest) { const token = request.cookies.get('auth-token') // Sprawdź istnienie i podstawowy format tokenu. // W produkcji zastąp tę weryfikację sprawdzeniem podpisu JWT // (np. jose.jwtVerify) lub wywołaniem własnego endpointu sesji. if (!token?.value) { return NextResponse.redirect(new URL('/login', request.url)) }}// Middleware uruchamia się TYLKO dla ścieżek /dashboard/*.// Bez tego konfiguratora Next.js odpala middleware na każdym żądaniu// (statyki, fonty, API), co jest zbędnym kosztem.export const config = { matcher: ['/dashboard/:path*'],}
Często zadawane pytania
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.
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.
Remarketing Google Ads w React i Next.js bez marketingowych uproszczeń: eventy, Merchant Center, listy odbiorców w GA4, Customer Match i wymogi consent.
Core Web Vitals to kluczowe metryki wydajności i doświadczenia użytkownika. Poznaj LCP, INP i CLS oraz zobacz, jak je mierzyć, monitorować i poprawiać w praktyce.