Kurs Next.js · Moduł 8: Deployment i produkcja
Integracja z Web Analytics
W tej lekcji13
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.
Popularne narzędzia analityczne
Do najczęściej używanych narzędzi analitycznych w aplikacjach Next.js należą:
- Google Analytics - kompleksowe narzędzie z szerokim zakresem funkcji analitycznych
- Vercel Analytics - natywna analityka dla aplikacji hostowanych na Vercel
- Plausible - przyjazna dla prywatności alternatywa open-source
- Fathom - prosty i skoncentrowany na prywatności system analityczny
- PostHog - platforma all-in-one łącząca analitykę, sesje użytkowników i funkcje eksperymentalne
- Mixpanel - zaawansowana analityka z naciskiem na śledzenie zdarzeń i lejki konwersji
- Amplitude - skupiona na analizie zachowań użytkowników i analizie produktowej
Integracja Google Analytics 4 (GA4)
Google Analytics to najpopularniejsze narzędzie analityczne. Oto jak zintegrować GA4 z aplikacją Next.js:
1. Konfiguracja Google Analytics
Zacznij od utworzenia konta Google Analytics i konfiguracji strumienia danych:
- Przejdź do Google Analytics
- Utwórz nowe konto i ustaw wskaźnik witryny
- Skonfiguruj strumień danych internetowych i zanotuj ID pomiaru (G-XXXXXXXX)
2. Implementacja w Next.js za pomocą App Router
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-XXXXXXXXXX3. Śledzenie nawigacji między stronami
W 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}4. Śledzenie niestandardowych zdarzeń
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}Integracja z Vercel Analytics
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.
1. Instalacja pakietu @vercel/analytics
1npm install @vercel/analytics
2# lub
3yarn add @vercel/analytics
4# lub
5pnpm add @vercel/analytics2. Dodanie providera analityki
1// 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.
3. Śledzenie niestandardowych zdarzeń w Vercel Analytics
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}Integracja przyjaznych dla prywatności narzędzi analitycznych
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.
Integracja 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}Integracja z PostHog
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}Obsługa RODO/GDPR i zgody użytkownika
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}Dostosowanie Google Analytics do wymogów RODO
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}Integracja kros-platform: Web, iOS, Android
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.
Przykład z Firebase Analytics
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}Pomiar Web Vitals
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}Własne panele analityczne
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}Testowanie i debugowanie analityki
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.
Najlepsze praktyki
- Szanuj prywatność użytkowników - zawsze informuj o użyciu narzędzi analitycznych i zbieraj zgodę
- Minimalizuj wpływ na wydajność - używaj strategii
afterInteractivelublazyOnloaddla skryptów analitycznych - Segmentuj dane - kategoryzuj zdarzenia, aby łatwiej analizować dane
- Korzystaj z wielu źródeł danych - łącz dane z różnych narzędzi dla pełniejszego obrazu
- Regularnie weryfikuj dane - sprawdzaj poprawność danych analitycznych
- Analizuj trendy - szukaj wzorców i anomalii w danych
- Anonimizuj dane - zbieraj dane w sposób zgodny z RODO
Frameworki analityczne dla Next.js
Dla bardziej zaawansowanych potrzeb, warto rozważyć użycie specjalizowanych frameworków analitycznych dla Next.js:
- Next-GA - uproszczona integracja z Google Analytics
- Use-Analytics - uniwersalny hook do integracji z różnymi dostawcami analitycznymi
- React-GA - popularna biblioteka do integracji React z Google Analytics
- Next-Plausible - integracja z przyjaznym dla prywatności narzędziem Plausible
Podsumowanie
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.
Kod do tej lekcji: App.tsx
1import React, { useState, useEffect } from 'react';
2
3// Demo: Web Analytics Integration
4interface AnalyticsEvent {
5 id: string;
6 type: 'pageview' | 'click' | 'scroll' | 'conversion' | 'error';
7 page: string;
8 timestamp: string;
9 data?: string;
10}
11
12interface AnalyticsMetrics {
13 pageViews: number;
14 uniqueVisitors: number;
15 bounceRate: number;
16 avgSessionDuration: number;
17 topPages: { page: string; views: number }[];
18}
19
20function AnalyticsDemo() {
21 const [events, setEvents] = useState<AnalyticsEvent[]>([]);
22 const [metrics, setMetrics] = useState<AnalyticsMetrics>({
23 pageViews: 1247,
24 uniqueVisitors: 892,
25 bounceRate: 34.2,
26 avgSessionDuration: 245,
27 topPages: [
28 { page: '/', views: 456 },
29 { page: '/blog', views: 312 },
30 { page: '/products', views: 278 },
31 { page: '/about', views: 201 },
32 ],
33 });
34 const [activeProvider, setActiveProvider] = useState<'ga4' | 'plausible' | 'custom'>('ga4');
35 const [isTracking, setIsTracking] = useState(true);
36
37 const providers = [
38 { id: 'ga4' as const, name: 'Google Analytics 4', color: '#f59e0b' },
39 { id: 'plausible' as const, name: 'Plausible', color: '#10b981' },
40 { id: 'custom' as const, name: 'Custom Analytics', color: '#8b5cf6' },
41 ];
42
43 const trackEvent = (type: AnalyticsEvent['type'], page: string, data?: string) => {
44 if (!isTracking) return;
45 const event: AnalyticsEvent = {
46 id: Math.random().toString(36).substr(2, 6),
47 type, page, data,
48 timestamp: new Date().toLocaleTimeString(),
49 };
50 setEvents(prev => [event, ...prev.slice(0, 19)]);
51 if (type === 'pageview') {
52 setMetrics(prev => ({ ...prev, pageViews: prev.pageViews + 1 }));
53 }
54 };
55
56 const eventColors: Record<string, string> = {
57 pageview: '#3b82f6', click: '#10b981', scroll: '#f59e0b', conversion: '#8b5cf6', error: '#ef4444',
58 };
59
60 const codeSnippets: Record<string, string> = {
61 ga4: "// app/layout.tsx\nimport { GoogleAnalytics } from '@next/third-parties/google';\n\nexport default function RootLayout({ children }) {\n return (\n <html>\n <body>{children}</body>\n <GoogleAnalytics gaId=\"G-XXXXXXX\" />\n </html>\n );\n}",
62 plausible: "// app/layout.tsx\nimport Script from 'next/script';\n\nexport default function RootLayout({ children }) {\n return (\n <html>\n <body>\n {children}\n <Script\n defer\n data-domain=\"yourdomain.com\"\n src=\"https://plausible.io/js/script.js\"\n />\n </body>\n </html>\n );\n}",
63 custom: "// lib/analytics.ts\nexport function trackPageView(url: string) {\n fetch('/api/analytics', {\n method: 'POST',\n body: JSON.stringify({ type: 'pageview', url, timestamp: Date.now() }),\n });\n}\n\n// app/layout.tsx - use in useEffect\nuseEffect(() => {\n trackPageView(pathname);\n}, [pathname]);",
64 };
65
66 return (
67 <div style={{ background: '#0f0f23', minHeight: '100vh', padding: '20px', color: '#e0e0e0', fontFamily: 'system-ui' }}>
68 <h1 style={{ color: '#64ffda', marginBottom: '8px' }}>Web Analytics Dashboard</h1>
69 <p style={{ color: '#8892b0', marginBottom: '24px' }}>Integracja analityki w Next.js</p>
70
71 <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px', marginBottom: '24px' }}>
72 {[
73 { label: 'Page Views', value: metrics.pageViews.toLocaleString(), color: '#3b82f6' },
74 { label: 'Unique Visitors', value: metrics.uniqueVisitors.toLocaleString(), color: '#10b981' },
75 { label: 'Bounce Rate', value: metrics.bounceRate + '%', color: '#f59e0b' },
76 { label: 'Avg Session', value: Math.floor(metrics.avgSessionDuration / 60) + 'm ' + (metrics.avgSessionDuration % 60) + 's', color: '#8b5cf6' },
77 ].map(m => (
78 <div key={m.label} style={{ background: '#1a1a2e', padding: '16px', borderRadius: '12px', border: '1px solid #333', textAlign: 'center' }}>
79 <div style={{ fontSize: '28px', fontWeight: 'bold', color: m.color }}>{m.value}</div>
80 <div style={{ fontSize: '13px', color: '#8892b0', marginTop: '4px' }}>{m.label}</div>
81 </div>
82 ))}
83 </div>
84
85 <div style={{ display: 'flex', gap: '8px', marginBottom: '20px' }}>
86 {providers.map(p => (
87 <button key={p.id} onClick={() => setActiveProvider(p.id)} style={{
88 padding: '8px 16px', borderRadius: '8px', border: 'none', cursor: 'pointer',
89 background: activeProvider === p.id ? p.color : '#1a1a2e', color: activeProvider === p.id ? '#fff' : '#8892b0', fontWeight: 'bold',
90 }}>{p.name}</button>
91 ))}
92 <button onClick={() => setIsTracking(!isTracking)} style={{ padding: '8px 16px', borderRadius: '8px', border: 'none', cursor: 'pointer', background: isTracking ? '#10b981' : '#ef4444', color: '#fff', marginLeft: 'auto' }}>
93 {isTracking ? 'Tracking ON' : 'Tracking OFF'}
94 </button>
95 </div>
96
97 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', marginBottom: '24px' }}>
98 <div>
99 <h3 style={{ color: '#64ffda', fontSize: '16px' }}>Symuluj zdarzenia</h3>
100 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
101 {[
102 { type: 'pageview' as const, label: 'Page View', page: '/home' },
103 { type: 'click' as const, label: 'Button Click', page: '/cta' },
104 { type: 'scroll' as const, label: 'Scroll 50%', page: '/blog' },
105 { type: 'conversion' as const, label: 'Conversion', page: '/checkout' },
106 { type: 'error' as const, label: 'JS Error', page: '/app' },
107 ].map(e => (
108 <button key={e.type} onClick={() => trackEvent(e.type, e.page, e.label)} style={{
109 padding: '10px', borderRadius: '8px', border: '1px solid ' + eventColors[e.type], background: eventColors[e.type] + '20', color: eventColors[e.type], cursor: 'pointer',
110 }}>{e.label}</button>
111 ))}
112 </div>
113 </div>
114 <div>
115 <h3 style={{ color: '#64ffda', fontSize: '16px' }}>Event Log</h3>
116 <div style={{ maxHeight: '200px', overflow: 'auto', background: '#1a1a2e', borderRadius: '8px', padding: '12px', border: '1px solid #333' }}>
117 {events.map(e => (
118 <div key={e.id} style={{ fontSize: '12px', padding: '4px 0', borderBottom: '1px solid #222', display: 'flex', gap: '8px' }}>
119 <span style={{ color: eventColors[e.type], minWidth: '80px' }}>{e.type}</span>
120 <span style={{ color: '#64ffda' }}>{e.page}</span>
121 <span style={{ color: '#555', marginLeft: 'auto' }}>{e.timestamp}</span>
122 </div>
123 ))}
124 {events.length === 0 && <p style={{ color: '#555', margin: 0 }}>Brak zdarzen</p>}
125 </div>
126 </div>
127 </div>
128
129 <div style={{ background: '#1a1a2e', padding: '16px', borderRadius: '8px', border: '1px solid #333' }}>
130 <h3 style={{ color: '#f59e0b', fontSize: '14px', marginTop: 0 }}>Kod integracji ({providers.find(p => p.id === activeProvider)?.name}):</h3>
131 <pre style={{ fontSize: '12px', color: '#e0e0e0', margin: 0, overflow: 'auto', whiteSpace: 'pre-wrap' }}>{codeSnippets[activeProvider]}</pre>
132 </div>
133 </div>
134 );
135}
136
137export default AnalyticsDemo;Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Core Web Vitals obejmują:
Zadania praktyczne w grze
- Edytor kodu
Stwórz panel kontrolny z monitorowaniem Web Vitals
- Klikanie w kolejności
Ułóż składnię funkcji reportWebVitals w Next.js
- Edytor kodu
Zaimplementuj dashboard zoptymalizowany pod kątem wydajności