From 08182d34e51720400d3f04c14c3ceba836337d28 Mon Sep 17 00:00:00 2001 From: Saurabh Singh Date: Mon, 27 Oct 2025 19:44:12 +0530 Subject: [PATCH] feat: Add Google Analytics integration with environment variable support --- .env.example | 4 + docs/GOOGLE_ANALYTICS_SETUP.md | 162 +++++++++++++++++++++++++++++++++ lib/gtag.ts | 45 +++++++++ package-lock.json | 1 + package.json | 3 +- pages/_app.tsx | 17 ++++ pages/_document.tsx | 23 +++++ tsconfig.json | 24 ++++- 8 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 .env.example create mode 100644 docs/GOOGLE_ANALYTICS_SETUP.md create mode 100644 lib/gtag.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fa0f00f --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# Google Analytics Tracking ID +# Get your tracking ID from https://analytics.google.com/ +# Format: G-XXXXXXXXXX (for GA4) or UA-XXXXXXXXX-X (for Universal Analytics) +NEXT_PUBLIC_GA_ID= diff --git a/docs/GOOGLE_ANALYTICS_SETUP.md b/docs/GOOGLE_ANALYTICS_SETUP.md new file mode 100644 index 0000000..2d74540 --- /dev/null +++ b/docs/GOOGLE_ANALYTICS_SETUP.md @@ -0,0 +1,162 @@ +# Google Analytics Integration Guide + +This document explains how to set up and use Google Analytics tracking on the Opsimate website. + +## Overview + +The website now includes Google Analytics (GA4) integration that: +- Respects user cookie consent preferences +- Only loads when a valid tracking ID is provided +- Tracks page views automatically +- Supports custom event tracking +- Uses environment variables to keep the tracking ID secure + +## Setup Instructions + +### 1. Get Your Google Analytics Tracking ID + +1. Go to [Google Analytics](https://analytics.google.com/) +2. Create a new property or use an existing one +3. Copy your Measurement ID (format: `G-XXXXXXXXXX`) + +### 2. Configure Environment Variable + +Create a `.env.local` file in the project root: + +```bash +NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX +``` + +Replace `G-XXXXXXXXXX` with your actual tracking ID. + +**Important:** Never commit `.env.local` to version control. It's already in `.gitignore`. + +### 3. Verify Setup + +1. Start the development server: + ```bash + npm run dev + ``` + +2. Open your browser's developer console +3. Check the Network tab for requests to `googletagmanager.com` +4. Visit different pages and verify tracking in GA Real-Time reports + +## How It Works + +### Files Modified/Created + +1. **`lib/gtag.ts`** - Core GA utility functions + - `isGAEnabled()` - Checks if GA tracking ID exists + - `pageview(url)` - Tracks page views + - `event()` - Tracks custom events + +2. **`pages/_document.tsx`** - Loads GA scripts + - Conditionally includes GA scripts only when tracking ID is set + - Initializes gtag with the tracking ID + +3. **`pages/_app.tsx`** - Tracks page navigation + - Listens to route changes + - Respects cookie consent preferences + - Only tracks when user has accepted cookies + +4. **`.env.example`** - Documents required environment variables + +### Cookie Consent Integration + +The GA tracking respects the existing cookie consent system: +- Only tracks when `hasConsent === true` +- Checks `localStorage` for `opsimateCookieConsent` +- Listens for consent changes via `cookieConsentChange` event + +## Custom Event Tracking + +You can track custom events anywhere in your application: + +```typescript +import * as gtag from '@/lib/gtag'; + +// Track a button click +gtag.event({ + action: 'click', + category: 'Button', + label: 'Download PDF', + value: 1 +}); + +// Track form submission +gtag.event({ + action: 'submit', + category: 'Form', + label: 'Contact Form' +}); +``` + +## Production Deployment + +### Vercel +Add the environment variable in your Vercel project settings: +1. Go to Project Settings → Environment Variables +2. Add `NEXT_PUBLIC_GA_ID` with your tracking ID +3. Redeploy + +### Docker +Pass the environment variable when running the container: +```bash +docker run -e NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX your-image +``` + +Or add it to your `docker-compose.yml`: +```yaml +environment: + - NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX +``` + +### Other Platforms +Consult your hosting provider's documentation for setting environment variables. + +## Testing + +### Development +1. Set `NEXT_PUBLIC_GA_ID` in `.env.local` +2. Run `npm run dev` +3. Open browser DevTools → Network tab +4. Navigate between pages +5. Verify GA requests are being sent + +### Production +1. Deploy with the environment variable set +2. Visit your live site +3. Check Google Analytics Real-Time reports +4. Verify page views are being tracked + +## Troubleshooting + +### GA Not Loading +- Verify `NEXT_PUBLIC_GA_ID` is set correctly +- Check browser console for errors +- Ensure ad blockers are disabled for testing +- Verify the tracking ID format is correct (G-XXXXXXXXXX) + +### Page Views Not Tracking +- Check cookie consent is accepted +- Verify route changes are triggering events +- Check GA Real-Time reports (may take a few seconds) + +### Events Not Tracking +- Ensure `gtag.isGAEnabled()` returns true +- Check event parameters are correct +- Verify user has accepted cookies + +## Privacy Considerations + +- GA only loads when user accepts cookies +- No tracking occurs without consent +- Tracking ID is not exposed in the codebase +- Complies with GDPR and privacy regulations + +## Additional Resources + +- [Google Analytics 4 Documentation](https://support.google.com/analytics/answer/10089681) +- [Next.js Analytics Guide](https://nextjs.org/docs/app/building-your-application/optimizing/analytics) +- [GA4 Event Tracking](https://developers.google.com/analytics/devguides/collection/ga4/events) diff --git a/lib/gtag.ts b/lib/gtag.ts new file mode 100644 index 0000000..7d4e542 --- /dev/null +++ b/lib/gtag.ts @@ -0,0 +1,45 @@ +// Google Analytics utility functions +// This file handles all GA tracking logic + +export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID; + +// Check if GA is enabled (tracking ID exists) +export const isGAEnabled = (): boolean => { + return !!GA_TRACKING_ID; +}; + +// Track page views +export const pageview = (url: string): void => { + if (!isGAEnabled()) return; + + window.gtag('config', GA_TRACKING_ID as string, { + page_path: url, + }); +}; + +// Track custom events +export const event = ({ action, category, label, value }: { + action: string; + category: string; + label?: string; + value?: number; +}): void => { + if (!isGAEnabled()) return; + + window.gtag('event', action, { + event_category: category, + event_label: label, + value: value, + }); +}; + +// Extend Window interface to include gtag +declare global { + interface Window { + gtag: ( + command: 'config' | 'event' | 'js', + targetId: string | Date, + config?: Record + ) => void; + } +} diff --git a/package-lock.json b/package-lock.json index b87103e..ea9121d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.15", + "@types/hast": "^3.0.4", "@types/node": "^20.8.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", diff --git a/package.json b/package.json index 95d6eca..75aaecb 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "remark": "^15.0.1" }, "devDependencies": { + "@tailwindcss/typography": "^0.5.15", + "@types/hast": "^3.0.4", "@types/node": "^20.8.0", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", @@ -29,7 +31,6 @@ "rehype-sanitize": "^6.0.0", "rehype-stringify": "^10.0.0", "remark-rehype": "^11.1.0", - "@tailwindcss/typography": "^0.5.15", "tailwindcss": "^3.3.0", "typescript": "^5.2.0" } diff --git a/pages/_app.tsx b/pages/_app.tsx index 6bd323e..0325d55 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -3,10 +3,13 @@ import { Analytics } from "@vercel/analytics/react"; import { ThemeProvider } from "../contexts/ThemeContext"; import "../styles/globals.css"; import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import * as gtag from "../lib/gtag"; const COOKIE_CONSENT_KEY = 'opsimateCookieConsent'; export default function App({ Component, pageProps }: AppProps) { + const router = useRouter(); const [hasConsent, setHasConsent] = useState(null); useEffect(() => { @@ -14,6 +17,20 @@ export default function App({ Component, pageProps }: AppProps) { setHasConsent(consent === 'accepted'); }, []); + // Track page views with Google Analytics + useEffect(() => { + if (!gtag.isGAEnabled() || hasConsent !== true) return; + + const handleRouteChange = (url: string) => { + gtag.pageview(url); + }; + + router.events.on('routeChangeComplete', handleRouteChange); + return () => { + router.events.off('routeChangeComplete', handleRouteChange); + }; + }, [router.events, hasConsent]); + useEffect(() => { const handleConsentChange = (event: Event) => { const customEvent = event as CustomEvent; diff --git a/pages/_document.tsx b/pages/_document.tsx index af0ff68..47efe86 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -1,9 +1,32 @@ import { Html, Head, Main, NextScript } from 'next/document'; +import { GA_TRACKING_ID, isGAEnabled } from '../lib/gtag'; export default function Document() { return ( + {/* Google Analytics */} + {isGAEnabled() && ( + <> +