We use cookies to enhance your experience on the site
CodeWorlds

Integration with Web Analytics

In the world of web applications, understanding user behavior and measuring effectiveness is key to making sound business and technical decisions. Integrating analytics tools with a Next.js application allows tracking traffic, user behavior, conversions, and other important performance indicators.

Popular analytics tools

The most commonly used analytics tools in Next.js applications include:

  1. Google Analytics - a comprehensive tool with a wide range of analytics features
  2. Vercel Analytics - native analytics for applications hosted on Vercel
  3. Plausible - privacy-friendly open-source alternative
  4. Fathom - simple and privacy-focused analytics system
  5. PostHog - all-in-one platform combining analytics, user sessions, and experimental features
  6. Mixpanel - advanced analytics with a focus on event tracking and conversion funnels
  7. Amplitude - focused on user behavior analysis and product analytics

Google Analytics 4 (GA4) integration

Google Analytics is the most popular analytics tool. Here's how to integrate GA4 with a Next.js application:

1. Google Analytics configuration

Start by creating a Google Analytics account and configuring a data stream:

  1. Go to Google Analytics
  2. Create a new account and set up a site indicator
  3. Configure a web data stream and note the measurement ID (G-XXXXXXXX)

2. Implementation in Next.js using App Router

Create a Google Analytics component:

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}

Then add the component to the main application layout:

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}

Make sure to add the environment variable to the file

.env.local
:

1NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX

3. Tracking navigation between pages

In App Router, you should implement URL change tracking because client-side navigation does not cause a full page refresh:

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}

Then use this hook in a client component:

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. Custom event tracking

Create a tool for tracking custom events:

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

Usage in a component:

1// components/SubscribeButton.tsx
2'use client';
3
4import { trackEvent } from '@/lib/analytics';
5
6export default function SubscribeButton() {
7  const handleSubscribe = () => {
8    // Subscription logic...
9    
10    // Event tracking
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      Subscribe to newsletter
24    </button>
25  );
26}

Integration with Vercel Analytics

If you host your Next.js application on Vercel, you can take advantage of Vercel's built-in analytics, which offers easy deployment and solid performance metrics.

1. Installing the @vercel/analytics package

1npm install @vercel/analytics
2# or
3yarn add @vercel/analytics
4# or
5pnpm add @vercel/analytics

2. Adding the analytics provider

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}

That's all! Vercel automatically collects data on page visits and key Web Vitals metrics.

3. Custom event tracking in 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    // Contact form logic...
13    
14    // Event tracking
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        Send
36      </button>
37    </form>
38  );
39}

Integration of privacy-friendly analytics tools

With growing privacy awareness and regulations such as GDPR, many companies choose privacy-friendly analytics tools. Below you will find an example of integration with Plausible Analytics.

Integration with 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}

Integration with PostHog

PostHog is a comprehensive open-source analytics platform that combines analytics, session recording, AB testing features, and more.

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    // Other options...
13    capture_pageview: false // Disable automatic page view tracking
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}

Handling GDPR and user consent

In accordance with personal data protection regulations, user consent must be obtained before starting tracking. Implementation of a simple cookie banner:

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    // Check if the user has already given consent
11    const storedConsent = localStorage.getItem('cookieConsent');
12    if (storedConsent !== null) {
13      setConsent(storedConsent === 'true');
14    } else {
15      setConsent(false); // No consent by default
16    }
17  }, []);
18  
19  const handleAccept = () => {
20    localStorage.setItem('cookieConsent', 'true');
21    setConsent(true);
22    
23    // Enable analytics
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    // Disable analytics
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; // Don't show banner if consent has been given or hasn't loaded yet
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            This page uses cookies to analyze traffic. You can decide whether to accept cookies.
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            Reject
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            Accept
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}

Adapting Google Analytics to GDPR requirements

Modify the Google Analytics initialization to take user consent into account:

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    // Check the user's consent
10    const consent = localStorage.getItem('cookieConsent') === 'true';
11    
12    // Update the consent state in 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            // Set default consent as 'denied'
36            gtag('consent', 'default', {
37              'analytics_storage': 'denied'
38            });
39            
40            // Configure Google Analytics
41            gtag('config', '${GA_MEASUREMENT_ID}', {
42              page_path: window.location.pathname,
43            });
44          `,
45        }}
46      />
47    </>
48  );
49}

Cross-platform integration: Web, iOS, Android

For applications that run on multiple platforms, it's worth implementing consistent analytics tracking. You can use libraries such as Segment, Amplitude or Firebase Analytics, which offer SDKs for different platforms.

Example with 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  // Other configuration...
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}

Measuring Web Vitals

Web Vitals metrics are key to understanding your application's performance. Next.js offers a built-in tool for reporting Web Vitals to analytics systems:

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    // Reporting to 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    // Reporting to the console in the development environment
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}

Custom analytics dashboards

Sometimes you need to visualize analytics data directly in your application. You can implement your own analytics dashboard by fetching data from the Google Analytics API or another tool:

1// app/admin/analytics/page.tsx
2import { google } from 'googleapis';
3import { BarChart, LineChart } from '@/components/Charts';
4
5async function getAnalyticsData() {
6  try {
7    // Initialize the Google Analytics client
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    // Fetch the analytics data
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    // Process the data
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('Error during fetching analytics data:', 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">Analytics dashboard</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">Sessions vs. page views</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">Page views by day</h2>
66          <BarChart 
67            data={analyticsData} 
68            category="pageviews" 
69            xKey="date" 
70          />
71        </div>
72      </div>
73    </div>
74  );
75}

Testing and debugging analytics

Before deploying to production, it's worth testing the analytics integration in the development environment:

1// lib/analytics.ts
2export function trackEvent(eventName: string, eventParams: any = {}) {
3  // In the development environment, print events to the console
4  if (process.env.NODE_ENV === 'development') {
5    console.log(`Analytics Event: ${eventName}`, eventParams);
6    return;
7  }
8  
9  // In the production environment, send to the analytics service
10  if (window.gtag) {
11    window.gtag('event', eventName, eventParams);
12  }
13}

For testing you can also use browser extensions such as Google Analytics Debugger, Tag Assistant or Facebook Pixel Helper.

Best practices

  1. Respect user privacy - always inform about the use of analytics tools and collect consent
  2. Minimize impact on performance - use the strategy
    afterInteractive
    or
    lazyOnload
    for analytics scripts
  3. Segment data - categorize events to make data easier to analyze
  4. Use multiple data sources - combine data from different tools for a fuller picture
  5. Regularly verify data - check the correctness of analytics data
  6. Analyze trends - look for patterns and anomalies in data
  7. Anonymize data - collect data in a GDPR-compliant manner

Analytics frameworks for Next.js

For more advanced needs, it's worth considering using specialized analytics frameworks for Next.js:

  1. Next-GA - simplified integration with Google Analytics
  2. Use-Analytics - a universal hook for integrating with different analytics providers
  3. React-GA - a popular library for integrating React with Google Analytics
  4. Next-Plausible - integration with the privacy-friendly tool Plausible

Summary

Web analytics is a key tool for understanding users and optimizing applications. Next.js offers many ways to integrate with various analytics tools, from Google Analytics through Vercel Analytics to more privacy-friendly alternatives such as Plausible or Fathom.

Remember that the most important thing is to collect only the data that is really needed and to do it with respect for user privacy. Good practices include obtaining consent, anonymizing data, and informing users about what data is collected and for what purpose.

Go to CodeWorlds