
PDF Download Analytics with Google Analytics 4
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:
- A route handler for serving PDFs
- An analytics utility for sending events to GA4
- 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
-
Store your PDFs in
src/content/pdfs/
-
Set up environment variables:
GA4_MEASUREMENT_ID=your-measurement-id
GA4_API_SECRET=your-api-secret
NEXT_PUBLIC_SITE_URL=https://yourdomain.com
- 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
- Server-Side Tracking: More reliable than client-side tracking
- Privacy Compliant: No PII collection
- Detailed Analytics: Track both successful and failed downloads
- Referrer Tracking: Understand traffic sources
- User Agent Analysis: Analyze device/browser patterns
- 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
- Requires GA4 (not compatible with Universal Analytics)
- Server-side tracking means some client information might not be available
Best Practices
- Use meaningful PDF filenames
- Implement error handling
- Monitor analytics regularly
- Keep GA4 credentials secure
- Consider implementing rate limiting for downloads
- Use page view tracking to understand user journey
Troubleshooting
Common issues and solutions:
- Missing GA4 credentials
- PDF not found errors
- 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