Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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=
162 changes: 162 additions & 0 deletions docs/GOOGLE_ANALYTICS_SETUP.md
Original file line number Diff line number Diff line change
@@ -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)
45 changes: 45 additions & 0 deletions lib/gtag.ts
Original file line number Diff line number Diff line change
@@ -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<string, any>
) => void;
}
}
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
}
Expand Down
17 changes: 17 additions & 0 deletions pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,34 @@ 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<boolean | null>(null);

useEffect(() => {
const consent = localStorage.getItem(COOKIE_CONSENT_KEY);
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;
Expand Down
23 changes: 23 additions & 0 deletions pages/_document.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Html lang="en">
<Head>
{/* Google Analytics */}
{isGAEnabled() && (
<>
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
/>
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`,
}}
/>
</>
)}

{/* Google Fonts - Inter for modern, clean typography */}
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
Expand Down
24 changes: 19 additions & 5 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "es6"],
"lib": [
"dom",
"dom.iterable",
"es6"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
Expand All @@ -13,15 +17,25 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"types": [],
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
"@/*": [
"./*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}