PDF Download Analytics with Google Analytics 4

PDF Download Analytics with Google Analytics 4

10 min read

PDF Download Analytics with Google Analytics 4

This solution provides server-side tracking of PDF downloads and page views using Google Analytics 4 (GA4). It's particularly useful when PDFs are stored in a content directory and served through a Next.js route handler.

Overview

The system consists of three main components:

  1. A route handler for serving PDFs
  2. An analytics utility for sending events to GA4
  3. A content organization structure for PDFs

Implementation

1. PDF Route Handler

// app/pdfs/[...slug]/route.ts
import { NextRequest } from 'next/server';
import { trackPdfDownload } from '@/lib/analytics';
import { join } from 'path';
import { readFile } from 'fs/promises';

interface RouteParams {
    params: Promise<{ slug: string[] }>;
}

export async function GET(request: NextRequest, { params }: RouteParams) {
    try {
        const resolvedParams = await params;
        const slugArray = resolvedParams.slug;
        const pdfPath = slugArray.join('/');
        const fullPath = join(process.cwd(), 'src/content/pdfs', `${pdfPath}`);

        // Read PDF
        const pdfBuffer = await readFile(fullPath);
        
        // Get request metadata
        const referer = request.headers.get('referer') || 'direct';
        const userAgent = request.headers.get('user-agent') || 'unknown';

        // Track download
        await trackPdfDownload(pdfPath, { referer, userAgent });

        const filename = slugArray[slugArray.length - 1];
        console.log(`[PDF] ${filename} - Agent: ${userAgent.split(' ')[0]} - Referer: ${referer}`);

        return new Response(pdfBuffer, {
            headers: {
                'Content-Type': 'application/pdf',
                'Content-Disposition': `inline; filename="${filename}"`,
            },
        });
    } catch (error) {
        console.error('[PDF] Error serving:', error);
        throw error;
    }
}

2. Analytics Utility

// lib/analytics.ts
import { headers } from 'next/headers';

interface AnalyticsEvent {
    name: string;
    params?: Record<string, string | number | boolean>;
    metadata?: {
        referer?: string;
        userAgent?: string;
        path?: string;
    };
}

interface PageViewParams {
    page_title: string;
    page_path: string;
    referer?: string;
}

const GA4_API_SECRET = process.env.GA4_API_SECRET;
const GA4_MEASUREMENT_ID = process.env.GA4_MEASUREMENT_ID;

export async function trackServerEvent(event: AnalyticsEvent) {
    if (!GA4_API_SECRET || !GA4_MEASUREMENT_ID) {
        console.warn('[Analytics] GA4 configuration missing');
        return;
    }

    const headersList = await headers();
    const userAgent = headersList.get('user-agent') || 'unknown';
    const referer = headersList.get('referer') || 'direct';

    const eventParams = {
        ...event.params,
        referer: event.metadata?.referer || referer,
        user_agent: event.metadata?.userAgent || userAgent,
        path: event.metadata?.path
    };

    const payload = {
        user_agent: userAgent,
        events: [{
            name: event.name,
            params: eventParams
        }]
    };

    try {
        const response = await fetch(
            `https://www.google-analytics.com/mp/collect?measurement_id=${GA4_MEASUREMENT_ID}&api_secret=${GA4_API_SECRET}`,
            {
                method: 'POST',
                body: JSON.stringify(payload),
                headers: { 'Content-Type': 'application/json' },
            }
        );

        if (!response.ok) {
            throw new Error(`Analytics API responded with ${response.status}`);
        }
        console.log(`[Analytics] ${event.name} - Agent: ${userAgent.split(' ')[0]} - Referer: ${eventParams.referer}`);
    } catch (error) {
        console.error('[Analytics] Error:', error);
    }
}

export async function trackPageView({ page_title, page_path, referer }: PageViewParams) {
    await trackServerEvent({
        name: 'page_view',
        params: {
            page_title,
            page_location: `${process.env.NEXT_PUBLIC_SITE_URL}${page_path}`,
            page_path,
            engagement_time_msec: 100
        },
        metadata: {
            referer,
            path: page_path
        }
    });
}

export async function trackPdfDownload(pdfPath: string, metadata?: { referer?: string; userAgent?: string }) {
    await trackServerEvent({
        name: 'pdf_download',
        params: {
            pdf_path: pdfPath,
            content_type: 'pdf'
        },
        metadata: {
            path: pdfPath,
            ...metadata
        }
    });
}

Setup

  1. Store your PDFs in src/content/pdfs/

  2. Set up environment variables:

GA4_MEASUREMENT_ID=your-measurement-id
GA4_API_SECRET=your-api-secret
NEXT_PUBLIC_SITE_URL=https://yourdomain.com
  1. Configure GA4:
    • Set up a new data stream in GA4
    • Get your Measurement ID and API Secret
    • Create custom events for 'pdf_download' and 'page_view'

What Gets Tracked

For each PDF download and page view, the following information is sent to GA4:

  • PDF path or page path
  • Referrer URL
  • User agent
  • Page title (for page views)
  • Page location (for page views)

Benefits

  1. Server-Side Tracking: More reliable than client-side tracking
  2. Privacy Compliant: No PII collection
  3. Detailed Analytics: Track both successful and failed downloads
  4. Referrer Tracking: Understand traffic sources
  5. User Agent Analysis: Analyze device/browser patterns
  6. Page View Tracking: Monitor page views alongside PDF downloads

GA4 Reports

You can create custom reports in GA4 to analyze:

  • Most downloaded PDFs
  • Traffic sources leading to downloads
  • Device types used for downloads
  • Geographic distribution of downloads (based on GA4's standard collection)
  • Page view patterns
  • User journey from page views to PDF downloads

Limitations

  1. Requires GA4 (not compatible with Universal Analytics)
  2. Server-side tracking means some client information might not be available

Best Practices

  1. Use meaningful PDF filenames
  2. Implement error handling
  3. Monitor analytics regularly
  4. Keep GA4 credentials secure
  5. Consider implementing rate limiting for downloads
  6. Use page view tracking to understand user journey

Troubleshooting

Common issues and solutions:

  1. Missing GA4 credentials
  2. PDF not found errors
  3. Analytics events not appearing in GA4

Important Note

Important Note: PDFs should be stored in src/content/pdfs/ and not in the /public directory. When using Vercel hosting, files in /public are served directly by the web server, bypassing our route handler and analytics tracking.

nextjsanalyticsgoogle-analyticsga4pdfserver-sidetracking