W świecie aplikacji webowych, zrozumienie zachowań użytkowników i mierzenie efektywności jest kluczowe dla podejmowania trafnych decyzji biznesowych i technicznych. Integracja narzędzi analitycznych z aplikacją Next.js pozwala na śledzenie ruchu, zachowania użytkowników, konwersji i innych istotnych wskaźników wydajności.
Do najczęściej używanych narzędzi analitycznych w aplikacjach Next.js należą:
Google Analytics to najpopularniejsze narzędzie analityczne. Oto jak zintegrować GA4 z aplikacją Next.js:
Zacznij od utworzenia konta Google Analytics i konfiguracji strumienia danych:
Utwórz komponent Google Analytics:
1// components/GoogleAnalytics.tsx
2'use client';
3
4import Script from 'next/script';
5
6export default function GoogleAnalytics({ GA_MEASUREMENT_ID }: { GA_MEASUREMENT_ID: string }) {
7 return (
8 <>
9 <Script
10 strategy="afterInteractive"
11 src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
12 />
13 <Script
14 id="google-analytics"
15 strategy="afterInteractive"
16 dangerouslySetInnerHTML={{
17 __html: `
18 window.dataLayer = window.dataLayer || [];
19 function gtag(){dataLayer.push(arguments);}
20 gtag('js', new Date());
21 gtag('config', '${GA_MEASUREMENT_ID}', {
22 page_path: window.location.pathname,
23 cookie_flags: 'SameSite=None;Secure'
24 });
25 `,
26 }}
27 />
28 </>
29 );
30}Następnie dodaj komponent do głównego layoutu aplikacji:
1// app/layout.tsx
2import GoogleAnalytics from '@/components/GoogleAnalytics';
3
4export default function RootLayout({
5 children,
6}: {
7 children: React.ReactNode;
8}) {
9 return (
10 <html lang="pl">
11 <body>
12 {process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID && (
13 <GoogleAnalytics GA_MEASUREMENT_ID={process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID} />
14 )}
15 {children}
16 </body>
17 </html>
18 );
19}Upewnij się, że dodasz zmienną środowiskową do pliku
.env.local:1NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXXW App Router, powinieneś zaimplementować śledzenie zmian URL, ponieważ nawigacja klientska nie powoduje pełnego odświeżenia strony:
1// hooks/usePageTracking.ts
2'use client';
3
4import { usePathname, useSearchParams } from 'next/navigation';
5import { useEffect } from 'react';
6
7export function usePageTracking() {
8 const pathname = usePathname();
9 const searchParams = useSearchParams();
10
11 useEffect(() => {
12 if (pathname && window.gtag) {
13 let url = window.origin + pathname;
14 if (searchParams.toString()) {
15 url = url + '?' + searchParams.toString();
16 }
17
18 window.gtag('config', process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID!, {
19 page_path: pathname,
20 page_location: url,
21 });
22 }
23 }, [pathname, searchParams]);
24}Następnie wykorzystaj ten hook w komponencie klienckim:
1// components/PageTracker.tsx
2'use client';
3
4import { usePageTracking } from '@/hooks/usePageTracking';
5
6export default function PageTracker() {
7 usePageTracking();
8 return null;
9}
10
11// app/layout.tsx
12import PageTracker from '@/components/PageTracker';
13
14export default function RootLayout({ children }: { children: React.ReactNode }) {
15 return (
16 <html lang="pl">
17 <body>
18 {process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID && (
19 <>
20 <GoogleAnalytics GA_MEASUREMENT_ID={process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID} />
21 <PageTracker />
22 </>
23 )}
24 {children}
25 </body>
26 </html>
27 );
28}Utwórz narzędzie do śledzenia niestandardowych zdarzeń:
1// lib/analytics.ts
2'use client';
3
4export type EventOptions = {
5 category: string;
6 label?: string;
7 value?: number;
8 nonInteraction?: boolean;
9 [key: string]: any;
10};
11
12export const trackEvent = (action: string, options: EventOptions) => {
13 const { category, label, value, nonInteraction = false, ...restOptions } = options;
14
15 if (typeof window !== 'undefined' && window.gtag) {
16 window.gtag('event', action, {
17 event_category: category,
18 event_label: label,
19 value: value,
20 non_interaction: nonInteraction,
21 ...restOptions,
22 });
23 }
24};Użycie w komponencie:
1// components/SubscribeButton.tsx
2'use client';
3
4import { trackEvent } from '@/lib/analytics';
5
6export default function SubscribeButton() {
7 const handleSubscribe = () => {
8 // Logika subskrypcji...
9
10 // Śledzenie zdarzenia
11 trackEvent('subscribe', {
12 category: 'Engagement',
13 label: 'Newsletter',
14 value: 1,
15 });
16 };
17
18 return (
19 <button
20 onClick={handleSubscribe}
21 className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded"
22 >
23 Zapisz się do newslettera
24 </button>
25 );
26}Jeśli hostujesz swoją aplikację Next.js na Vercel, możesz skorzystać z wbudowanej analityki Vercel, która oferuje proste wdrożenie i solidne metryki wydajności.
1npm install @vercel/analytics
2# lub
3yarn add @vercel/analytics
4# lub
5pnpm add @vercel/analytics1// app/layout.tsx
2import { Analytics } from '@vercel/analytics/react';
3
4export default function RootLayout({
5 children,
6}: {
7 children: React.ReactNode;
8}) {
9 return (
10 <html lang="pl">
11 <body>
12 {children}
13 <Analytics />
14 </body>
15 </html>
16 );
17}To wszystko! Vercel automatycznie zbiera dane dotyczące odwiedzin stron i kluczowych metryk Web Vitals.
1// components/ContactForm.tsx
2'use client';
3
4import { track } from '@vercel/analytics';
5import { useState } from 'react';
6
7export default function ContactForm() {
8 const [email, setEmail] = useState('');
9
10 const handleSubmit = (e: React.FormEvent) => {
11 e.preventDefault();
12 // Logika formularza kontaktowego...
13
14 // Śledzenie zdarzenia
15 track('contact_form_submitted', { email_domain: email.split('@')[1] });
16 };
17
18 return (
19 <form onSubmit={handleSubmit} className="space-y-4">
20 <div>
21 <label htmlFor="email" className="block mb-1">Email</label>
22 <input
23 id="email"
24 type="email"
25 value={email}
26 onChange={(e) => setEmail(e.target.value)}
27 required
28 className="w-full px-3 py-2 border rounded"
29 />
30 </div>
31 <button
32 type="submit"
33 className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded"
34 >
35 Wyślij
36 </button>
37 </form>
38 );
39}Z rosnącą świadomością prywatności i regulacjami jak RODO/GDPR, wiele firm wybiera narzędzia analityczne, które są przyjazne dla prywatności. Poniżej znajdziesz przykład integracji z Plausible Analytics.
1// components/PlausibleAnalytics.tsx
2'use client';
3
4import Script from 'next/script';
5
6export default function PlausibleAnalytics({ domain }: { domain: string }) {
7 return (
8 <Script
9 strategy="afterInteractive"
10 data-domain={domain}
11 src="https://plausible.io/js/script.js"
12 />
13 );
14}
15
16// app/layout.tsx
17import PlausibleAnalytics from '@/components/PlausibleAnalytics';
18
19export default function RootLayout({
20 children,
21}: {
22 children: React.ReactNode;
23}) {
24 return (
25 <html lang="pl">
26 <body>
27 {process.env.NEXT_PUBLIC_ANALYTICS_DOMAIN && (
28 <PlausibleAnalytics domain={process.env.NEXT_PUBLIC_ANALYTICS_DOMAIN} />
29 )}
30 {children}
31 </body>
32 </html>
33 );
34}PostHog to kompleksowa platforma analityczna open-source, która łączy w sobie analitykę, nagrywanie sesji, funkcje AB testów i inne.
1// app/providers.tsx
2'use client';
3
4import posthog from 'posthog-js';
5import { PostHogProvider } from 'posthog-js/react';
6import { usePathname, useSearchParams } from 'next/navigation';
7import { useEffect } from 'react';
8
9if (typeof window !== 'undefined' && process.env.NEXT_PUBLIC_POSTHOG_KEY) {
10 posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
11 api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com',
12 // Inne opcje...
13 capture_pageview: false // Wyłącz automatyczne śledzenie widoku strony
14 });
15}
16
17export function PostHogPageview(): JSX.Element {
18 const pathname = usePathname();
19 const searchParams = useSearchParams();
20
21 useEffect(() => {
22 if (pathname) {
23 let url = window.origin + pathname;
24 if (searchParams && searchParams.toString()) {
25 url = url + `?${searchParams.toString()}`;
26 }
27 posthog.capture('$pageview', {
28 $current_url: url,
29 });
30 }
31 }, [pathname, searchParams]);
32
33 return <></>;
34}
35
36export function PHProvider({ children }: { children: React.ReactNode }) {
37 return (
38 <PostHogProvider client={posthog}>
39 <PostHogPageview />
40 {children}
41 </PostHogProvider>
42 );
43}
44
45// app/layout.tsx
46import { PHProvider } from './providers';
47
48export default function RootLayout({
49 children,
50}: {
51 children: React.ReactNode;
52}) {
53 return (
54 <html lang="pl">
55 <body>
56 <PHProvider>{children}</PHProvider>
57 </body>
58 </html>
59 );
60}Zgodnie z przepisami o ochronie danych osobowych, należy uzyskać zgodę użytkownika przed rozpoczęciem śledzenia. Implementacja prostego banner z informacją o cookies:
1// components/CookieConsent.tsx
2'use client';
3
4import { useState, useEffect } from 'react';
5
6export default function CookieConsent() {
7 const [consent, setConsent] = useState<boolean | null>(null);
8
9 useEffect(() => {
10 // Sprawdzenie, czy użytkownik już wyraził zgodę
11 const storedConsent = localStorage.getItem('cookieConsent');
12 if (storedConsent !== null) {
13 setConsent(storedConsent === 'true');
14 } else {
15 setConsent(false); // Domyślnie brak zgody
16 }
17 }, []);
18
19 const handleAccept = () => {
20 localStorage.setItem('cookieConsent', 'true');
21 setConsent(true);
22
23 // Włącz analitykę
24 if (window.gtag) {
25 window.gtag('consent', 'update', {
26 'analytics_storage': 'granted'
27 });
28 }
29 };
30
31 const handleReject = () => {
32 localStorage.setItem('cookieConsent', 'false');
33 setConsent(false);
34
35 // Wyłącz analitykę
36 if (window.gtag) {
37 window.gtag('consent', 'update', {
38 'analytics_storage': 'denied'
39 });
40 }
41 };
42
43 if (consent === null || consent === true) {
44 return null; // Nie pokazuj bannera, jeśli zgoda została wyrażona lub jeszcze nie załadowano
45 }
46
47 return (
48 <div className="fixed bottom-0 left-0 right-0 bg-gray-100 p-4 shadow-md z-50">
49 <div className="max-w-6xl mx-auto flex flex-col md:flex-row items-center justify-between">
50 <div className="mb-4 md:mb-0">
51 <p className="text-sm">
52 Ta strona używa plików cookie do analizy ruchu. Możesz zdecydować, czy chcesz zaakceptować pliki cookie.
53 </p>
54 </div>
55 <div className="flex space-x-4">
56 <button
57 onClick={handleReject}
58 className="px-4 py-2 text-sm border border-gray-300 rounded hover:bg-gray-200"
59 >
60 Odrzuć
61 </button>
62 <button
63 onClick={handleAccept}
64 className="px-4 py-2 text-sm bg-blue-500 text-white rounded hover:bg-blue-600"
65 >
66 Akceptuj
67 </button>
68 </div>
69 </div>
70 </div>
71 );
72}
73
74// app/layout.tsx
75import CookieConsent from '@/components/CookieConsent';
76
77export default function RootLayout({
78 children,
79}: {
80 children: React.ReactNode;
81}) {
82 return (
83 <html lang="pl">
84 <body>
85 {children}
86 <CookieConsent />
87 </body>
88 </html>
89 );
90}Zmodyfikuj inicjalizację Google Analytics, aby uwzględnić zgodę użytkownika:
1// components/GoogleAnalytics.tsx
2'use client';
3
4import Script from 'next/script';
5import { useEffect } from 'react';
6
7export default function GoogleAnalytics({ GA_MEASUREMENT_ID }: { GA_MEASUREMENT_ID: string }) {
8 useEffect(() => {
9 // Sprawdzenie zgody użytkownika
10 const consent = localStorage.getItem('cookieConsent') === 'true';
11
12 // Aktualizacja stanu zgody w Google Analytics
13 if (window.gtag) {
14 window.gtag('consent', 'update', {
15 'analytics_storage': consent ? 'granted' : 'denied'
16 });
17 }
18 }, []);
19
20 return (
21 <>
22 <Script
23 strategy="afterInteractive"
24 src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
25 />
26 <Script
27 id="google-analytics"
28 strategy="afterInteractive"
29 dangerouslySetInnerHTML={{
30 __html: `
31 window.dataLayer = window.dataLayer || [];
32 function gtag(){dataLayer.push(arguments);}
33 gtag('js', new Date());
34
35 // Ustaw domyślną zgodę jako 'denied'
36 gtag('consent', 'default', {
37 'analytics_storage': 'denied'
38 });
39
40 // Skonfiguruj Google Analytics
41 gtag('config', '${GA_MEASUREMENT_ID}', {
42 page_path: window.location.pathname,
43 });
44 `,
45 }}
46 />
47 </>
48 );
49}Dla aplikacji, które działają na wielu platformach, warto zaimplementować spójne śledzenie analityczne. Możesz skorzystać z bibliotek takich jak Segment, Amplitude lub Firebase Analytics, które oferują SDK dla różnych platform.
1// lib/analytics.ts
2import { initializeApp } from 'firebase/app';
3import { getAnalytics, logEvent, isSupported } from 'firebase/analytics';
4
5const firebaseConfig = {
6 apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
7 authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
8 projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
9 // Inne konfiguracje...
10};
11
12let analytics: any = null;
13
14export async function initAnalytics() {
15 if (typeof window !== 'undefined' && await isSupported()) {
16 const app = initializeApp(firebaseConfig);
17 analytics = getAnalytics(app);
18 return true;
19 }
20 return false;
21}
22
23export function trackEvent(eventName: string, eventParams?: any) {
24 if (analytics) {
25 logEvent(analytics, eventName, eventParams);
26 }
27}
28
29// components/AnalyticsProvider.tsx
30'use client';
31
32import { useEffect } from 'react';
33import { usePathname, useSearchParams } from 'next/navigation';
34import { initAnalytics, trackEvent } from '@/lib/analytics';
35
36export default function AnalyticsProvider({ children }: { children: React.ReactNode }) {
37 const pathname = usePathname();
38 const searchParams = useSearchParams();
39
40 useEffect(() => {
41 initAnalytics();
42 }, []);
43
44 useEffect(() => {
45 if (pathname) {
46 trackEvent('page_view', {
47 page_path: pathname,
48 page_search: searchParams?.toString(),
49 });
50 }
51 }, [pathname, searchParams]);
52
53 return <>{children}</>;
54}Metryki Web Vitals są kluczowe dla zrozumienia wydajności twojej aplikacji. Next.js oferuje wbudowane narzędzie do raportowania Web Vitals do systemów analitycznych:
1// app/layout.tsx
2import { useReportWebVitals } from 'next/web-vitals';
3
4export function WebVitals() {
5 useReportWebVitals((metric) => {
6 const { id, name, label, value } = metric;
7
8 // Raportowanie do Google Analytics
9 if (window.gtag) {
10 window.gtag('event', name, {
11 event_category: 'Web Vitals',
12 event_label: label,
13 value: Math.round(name === 'CLS' ? value * 1000 : value),
14 non_interaction: true,
15 metric_id: id,
16 });
17 }
18
19 // Raportowanie do konsoli w środowisku deweloperskim
20 if (process.env.NODE_ENV === 'development') {
21 console.log(`Web Vital: ${name}`, metric);
22 }
23 });
24
25 return null;
26}
27
28export default function RootLayout({ children }: { children: React.ReactNode }) {
29 return (
30 <html lang="pl">
31 <body>
32 <WebVitals />
33 {children}
34 </body>
35 </html>
36 );
37}Czasami potrzebujesz wizualizować dane analityczne bezpośrednio w swojej aplikacji. Możesz zaimplementować własny dashboard analityczny, pobierając dane z API Google Analytics lub innego narzędzia:
1// app/admin/analytics/page.tsx
2import { google } from 'googleapis';
3import { BarChart, LineChart } from '@/components/Charts';
4
5async function getAnalyticsData() {
6 try {
7 // Inicjalizacja klienta Google Analytics
8 const auth = new google.auth.GoogleAuth({
9 keyFile: process.env.GOOGLE_APPLICATION_CREDENTIALS,
10 scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
11 });
12
13 const analyticsreporting = google.analyticsreporting({
14 version: 'v4',
15 auth,
16 });
17
18 // Pobranie danych analitycznych
19 const response = await analyticsreporting.reports.batchGet({
20 requestBody: {
21 reportRequests: [
22 {
23 viewId: process.env.GA_VIEW_ID,
24 dateRanges: [{ startDate: '30daysAgo', endDate: 'today' }],
25 metrics: [{ expression: 'ga:sessions' }, { expression: 'ga:pageviews' }],
26 dimensions: [{ name: 'ga:date' }],
27 },
28 ],
29 },
30 });
31
32 // Przetworzenie danych
33 const report = response.data.reports[0];
34 const rows = report.data.rows;
35
36 return rows.map((row) => ({
37 date: row.dimensions[0],
38 sessions: parseInt(row.metrics[0].values[0], 10),
39 pageviews: parseInt(row.metrics[0].values[1], 10),
40 }));
41 } catch (error) {
42 console.error('Błąd podczas pobierania danych analitycznych:', error);
43 return [];
44 }
45}
46
47export default async function AnalyticsDashboard() {
48 const analyticsData = await getAnalyticsData();
49
50 return (
51 <div className="p-6">
52 <h1 className="text-2xl font-bold mb-6">Panel analityczny</h1>
53
54 <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
55 <div className="bg-white p-4 rounded shadow">
56 <h2 className="text-lg font-semibold mb-4">Sesje vs. odsłony strony</h2>
57 <LineChart
58 data={analyticsData}
59 categories={['sessions', 'pageviews']}
60 xKey="date"
61 />
62 </div>
63
64 <div className="bg-white p-4 rounded shadow">
65 <h2 className="text-lg font-semibold mb-4">Odsłony według dnia</h2>
66 <BarChart
67 data={analyticsData}
68 category="pageviews"
69 xKey="date"
70 />
71 </div>
72 </div>
73 </div>
74 );
75}Przed wdrożeniem na produkcję, warto przetestować integrację analityczną w środowisku deweloperskim:
1// lib/analytics.ts
2export function trackEvent(eventName: string, eventParams: any = {}) {
3 // W środowisku deweloperskim wypisuj zdarzenia do konsoli
4 if (process.env.NODE_ENV === 'development') {
5 console.log(`Analytics Event: ${eventName}`, eventParams);
6 return;
7 }
8
9 // W środowisku produkcyjnym wysyłaj do serwisu analitycznego
10 if (window.gtag) {
11 window.gtag('event', eventName, eventParams);
12 }
13}Do testowania możesz także użyć dodatków przeglądarkowych, takich jak Google Analytics Debugger, Tag Assistant lub Facebook Pixel Helper.
afterInteractive lub lazyOnload dla skryptów analitycznychDla bardziej zaawansowanych potrzeb, warto rozważyć użycie specjalizowanych frameworków analitycznych dla Next.js:
Analityka webowa jest kluczowym narzędziem do zrozumienia użytkowników i optymalizacji aplikacji. Next.js oferuje wiele sposobów integracji z różnymi narzędziami analitycznymi, od Google Analytics przez Vercel Analytics po alternatywy bardziej przyjazne dla prywatności, takie jak Plausible czy Fathom.
Pamiętaj, że najważniejsze jest, aby zbierać tylko te dane, które są naprawdę potrzebne i robić to z poszanowaniem prywatności użytkowników. Dobre praktyki obejmują uzyskiwanie zgody, anonimizację danych i informowanie użytkowników o tym, jakie dane są zbierane i w jakim celu.