Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:32:40 +08:00
commit 0ea8352871
72 changed files with 30043 additions and 0 deletions

View File

@@ -0,0 +1,645 @@
# gtag.js Complete Command Reference
**Version:** GA4 (2025)
**Source:** Google Analytics Developer Documentation
**Last Updated:** November 2025
---
## Overview
gtag.js provides four core commands for implementing Google Analytics 4 tracking. This reference documents all command variants with complete syntax, parameters, and examples.
---
## Core Commands
### 1. gtag('js', dateObject)
**Purpose:** Initialize gtag.js library with timestamp
**Syntax:**
```javascript
gtag('js', new Date());
```
**Parameters:**
- `dateObject` (Date) - Current timestamp
**Required:** Yes, appears once in base snippet
**Called:** Once per page load
**Position:** Immediately after gtag() function definition
**Example:**
```javascript
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date()); // Initialize library
gtag('config', 'G-XXXXXXXXXX'); // Then configure
```
---
### 2. gtag('config', measurementId, configObject)
**Purpose:** Initialize and configure GA4 property with optional default parameters
**Syntax:**
```javascript
gtag('config', 'MEASUREMENT_ID');
gtag('config', 'MEASUREMENT_ID', configObject);
```
**Parameters:**
**measurementId** (string, required)
- Format: `G-XXXXXXXXXX`
- Example: `G-3K8Y9Z2L4M`
- Location: GA4 Admin → Data Streams → Web Stream
**configObject** (object, optional)
- Configuration parameters sent with all events from this tag
**Common Configuration Parameters:**
| Parameter | Type | Purpose | Example |
|-----------|------|---------|---------|
| `page_title` | string | Override page title | `'My Custom Title'` |
| `page_location` | string | Override page URL | `window.location.href` |
| `send_page_view` | boolean | Auto-send page_view | `true` (default) |
| `allow_google_signals` | boolean | Enable remarketing | `true` |
| `allow_ad_personalization_signals` | boolean | Ad personalization | `true` |
| `user_id` | string | Custom user ID | `'user_12345'` |
| `debug_mode` | boolean | Enable DebugView | `true` |
**Basic Example:**
```javascript
gtag('config', 'G-XXXXXXXXXX');
```
**With Configuration:**
```javascript
gtag('config', 'G-XXXXXXXXXX', {
'page_title': document.title,
'page_location': window.location.href,
'user_id': 'user_12345',
'send_page_view': true,
'allow_google_signals': true
});
```
**Disable Automatic Page View:**
```javascript
gtag('config', 'G-XXXXXXXXXX', {
'send_page_view': false
});
// Then manually send page views in SPA:
gtag('event', 'page_view', {
'page_title': 'New Page',
'page_location': '/new-page'
});
```
**Multiple Properties:**
```javascript
// Track to multiple GA4 properties
gtag('config', 'G-PROPERTY-1');
gtag('config', 'G-PROPERTY-2');
```
---
### 3. gtag('event', eventName, eventParameters)
**Purpose:** Send an event to Google Analytics 4
**Syntax:**
```javascript
gtag('event', 'EVENT_NAME');
gtag('event', 'EVENT_NAME', eventParameters);
```
**Parameters:**
**eventName** (string, required)
- Maximum 40 characters
- snake_case convention
- Alphanumeric + underscores only
- Must start with alphabetic character
- Examples: `'purchase'`, `'sign_up'`, `'video_tutorial_watched'`
**eventParameters** (object, optional)
- Maximum 25 parameters per event
- Parameter names: 40 character limit
- Parameter values: 100 characters (exceptions: page_title 300, page_referrer 420, page_location 1000)
**Simple Event:**
```javascript
gtag('event', 'page_view');
```
**Event with Parameters:**
```javascript
gtag('event', 'button_click', {
'button_name': 'Subscribe Now',
'button_location': 'header',
'button_id': 'btn_subscribe_01'
});
```
**Event with Value (Conversion):**
```javascript
gtag('event', 'purchase', {
'value': 99.99,
'currency': 'USD',
'transaction_id': 'TXN_' + Date.now()
});
```
---
## Event-Specific Commands
### login Event
**Purpose:** Track user authentication
**Syntax:**
```javascript
gtag('event', 'login', {
'method': 'METHOD'
});
```
**Parameters:**
- `method` (string) - Authentication method: `'email'`, `'google'`, `'facebook'`, `'phone'`, `'social'`
**Example:**
```javascript
// After successful login
gtag('event', 'login', {
'method': 'google'
});
```
---
### sign_up Event
**Purpose:** Track new user registration
**Syntax:**
```javascript
gtag('event', 'sign_up', {
'method': 'METHOD'
});
```
**Parameters:**
- `method` (string) - Signup method: `'email'`, `'social'`
**Example:**
```javascript
gtag('event', 'sign_up', {
'method': 'email'
});
```
---
### view_item Event
**Purpose:** Track product page view
**Syntax:**
```javascript
gtag('event', 'view_item', {
'items': itemsArray,
'value': number,
'currency': 'CURRENCY_CODE'
});
```
**Required Parameters:**
- `items` (array) - Array of item objects
**Optional Parameters:**
- `value` (number) - Total value
- `currency` (string) - Currency code (ISO 4217)
**Example:**
```javascript
gtag('event', 'view_item', {
'items': [
{
'item_id': 'SKU_12345',
'item_name': 'Premium Widget',
'item_category': 'Widgets',
'item_brand': 'My Brand',
'price': 49.99,
'quantity': 1
}
],
'value': 49.99,
'currency': 'USD'
});
```
---
### add_to_cart Event
**Purpose:** Track item added to shopping cart
**Syntax:**
```javascript
gtag('event', 'add_to_cart', {
'items': itemsArray,
'value': number,
'currency': 'CURRENCY_CODE'
});
```
**Required Parameters:**
- `items` (array) - Array of item objects with at least `item_id` or `item_name`
**Optional Parameters:**
- `value` (number) - Total cart value
- `currency` (string) - Currency code
**Example:**
```javascript
gtag('event', 'add_to_cart', {
'items': [
{
'item_id': 'SKU_12345',
'item_name': 'Premium Widget',
'price': 49.99,
'quantity': 2
}
],
'value': 99.98,
'currency': 'USD'
});
```
---
### begin_checkout Event
**Purpose:** Track checkout process initiation
**Syntax:**
```javascript
gtag('event', 'begin_checkout', {
'items': itemsArray,
'value': number,
'currency': 'CURRENCY_CODE',
'coupon': 'COUPON_CODE'
});
```
**Required Parameters:**
- `items` (array) - Array of items in cart
- `value` (number) - Total cart value
- `currency` (string) - Currency code
**Optional Parameters:**
- `coupon` (string) - Coupon code applied
**Example:**
```javascript
gtag('event', 'begin_checkout', {
'items': [
{
'item_id': 'SKU_001',
'item_name': 'Product A',
'price': 29.99,
'quantity': 2
},
{
'item_id': 'SKU_002',
'item_name': 'Product B',
'price': 39.99,
'quantity': 1
}
],
'value': 99.97,
'currency': 'USD',
'coupon': 'SUMMER20'
});
```
---
### purchase Event
**Purpose:** Track completed transaction (most important ecommerce event)
**Syntax:**
```javascript
gtag('event', 'purchase', {
'transaction_id': 'UNIQUE_ID',
'value': number,
'currency': 'CURRENCY_CODE',
'tax': number,
'shipping': number,
'coupon': 'COUPON_CODE',
'items': itemsArray
});
```
**Required Parameters:**
- `transaction_id` (string) - Unique transaction identifier (prevents duplicate reporting)
- `value` (number) - Total transaction value
- `currency` (string) - Currency code (ISO 4217)
**Highly Recommended Parameters:**
- `items` (array) - Array of purchased items
- `tax` (number) - Tax amount
- `shipping` (number) - Shipping cost
**Optional Parameters:**
- `coupon` (string) - Transaction-level coupon
- `affiliation` (string) - Store/vendor name
**Complete Example:**
```javascript
gtag('event', 'purchase', {
'transaction_id': 'TXN_' + Date.now(), // Unique ID
'affiliation': 'Google Merchandise Store',
'value': 142.52,
'currency': 'USD',
'tax': 10.00,
'shipping': 5.00,
'coupon': 'SUMMER_FUN',
'items': [
{
'item_id': 'SKU_12345',
'item_name': 'Stan and Friends Tee',
'affiliation': 'Google Merchandise Store',
'coupon': 'SUMMER_FUN',
'discount': 2.22,
'index': 0,
'item_brand': 'Google',
'item_category': 'Apparel',
'item_category2': 'Adult',
'item_category3': 'Shirts',
'item_variant': 'green',
'price': 10.01,
'quantity': 3
},
{
'item_id': 'SKU_67890',
'item_name': 'Google Sunglasses',
'item_brand': 'Google',
'item_category': 'Accessories',
'price': 99.99,
'quantity': 1
}
]
});
```
---
### 4. gtag('set', userProperties)
**Purpose:** Set user-level properties that persist across sessions
**Syntax:**
```javascript
gtag('set', userPropertiesObject);
```
**Parameters:**
**userPropertiesObject** (object)
- Key-value pairs of user properties
- Property names: 24 character limit
- Property values: 36 character limit
- Persist across sessions until cleared or user session ends
**User ID Example:**
```javascript
// After user login
gtag('set', {
'user_id': 'user_' + userId
});
// CRITICAL: On logout - use null, NOT empty string
gtag('set', {
'user_id': null // Correct
});
// WRONG - do not use empty string
gtag('set', {
'user_id': '' // WRONG - will cause issues
});
```
**Custom User Properties:**
```javascript
gtag('set', {
'subscription_tier': 'premium',
'customer_lifetime_value': 5000,
'years_customer': 3,
'preferred_language': 'en',
'account_type': 'business'
});
```
**Important Notes:**
- User properties must be registered as custom dimensions in GA4 Admin
- Properties persist for the user's session
- Clear with `null` value, never empty string `""`
- Changes may take 24-48 hours to appear in reports
---
## Items Array Complete Structure
The `items` array is used in ecommerce events (`view_item`, `add_to_cart`, `begin_checkout`, `purchase`).
### Required Fields (at least one)
```javascript
{
'item_id': 'SKU_123', // OR
'item_name': 'Product Name'
}
```
### Highly Recommended Fields
```javascript
{
'item_id': 'SKU_123',
'item_name': 'Product Name',
'price': 99.99,
'quantity': 1,
'item_category': 'Electronics'
}
```
### All Available Fields
```javascript
{
// Required (at least one)
'item_id': 'SKU_123',
'item_name': 'Product Name',
// Highly Recommended
'price': 99.99,
'quantity': 1,
'item_category': 'Electronics',
// Categories (hierarchical)
'item_category2': 'Computers',
'item_category3': 'Laptops',
'item_category4': 'Gaming',
'item_category5': '17-inch',
// Product Details
'item_brand': 'Brand Name',
'item_variant': 'Blue/Large',
// Transaction Details
'coupon': 'ITEM_COUPON',
'discount': 10.00,
'affiliation': 'Store Name',
// List Details
'index': 0, // Position in list (0-based)
'item_list_id': 'related_products',
'item_list_name': 'Related Products',
// Location
'location_id': 'L_12345'
}
```
---
## Advanced Usage Patterns
### Conditional Event Firing
```javascript
// Only track for specific user groups
if (userRole === 'premium') {
gtag('event', 'premium_feature_access', {
'feature_name': 'Advanced Analytics'
});
}
```
### Dynamic Parameter Population
```javascript
// Get data from page elements
const productId = document.querySelector('[data-product-id]').textContent;
const productName = document.querySelector('.product-title').textContent;
const productPrice = parseFloat(document.querySelector('.product-price').textContent);
gtag('event', 'view_item', {
'items': [{
'item_id': productId,
'item_name': productName,
'price': productPrice
}]
});
```
### Delayed Event Tracking
```javascript
// Track after form submission completes
document.getElementById('contact-form').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
// Send event
gtag('event', 'form_submit', {
'form_name': 'Contact Form',
'email_domain': formData.get('email').split('@')[1]
});
// Then submit form
setTimeout(() => {
this.submit();
}, 100);
});
```
---
## Parameter Limits & Constraints
**Event Names:**
- Maximum 40 characters
- snake_case (lowercase with underscores)
- Alphanumeric + underscores only
- Must start with alphabetic character
**Parameter Names:**
- Maximum 40 characters
- Same naming rules as event names
**Parameter Values:**
- Standard: 100 characters
- Exception: `page_title` (300 chars)
- Exception: `page_referrer` (420 chars)
- Exception: `page_location` (1000 chars)
**Count Limits:**
- Maximum 25 parameters per event
- Maximum 200 items in items array
- Maximum 27 custom item parameters
**User Properties:**
- Property names: 24 characters
- Property values: 36 characters
---
## Common Errors & Solutions
| Error | Cause | Solution |
|-------|-------|----------|
| Events not firing | gtag() called before initialization | Verify gtag snippet loaded first |
| Duplicate events | Multiple gtag snippets | Remove duplicate implementations |
| User ID not persisting | Set to empty string `""` | Always use `null` to clear |
| Custom parameters missing | Not registered as dimensions | Register in GA4 Admin → Custom Definitions |
| Data delayed 24+ hours | Normal aggregation | Use DebugView for immediate validation |
| Items array errors | Missing required fields | Include `item_id` OR `item_name` |
---
## Best Practices
**DO:**
- ✅ Place gtag snippet in `<head>` before other scripts
- ✅ Use async attribute on script tag
- ✅ Validate events with DebugView before production
- ✅ Use descriptive event names (snake_case)
- ✅ Clear User ID with `null` on logout
- ✅ Include `transaction_id` in purchase events
**DON'T:**
- ❌ Call gtag() before initialization
- ❌ Use multiple gtag snippets on same page
- ❌ Use empty string to clear User ID (use `null`)
- ❌ Send PII in parameters
- ❌ Use generic event names (`'event1'`, `'click'`)
- ❌ Exceed parameter limits
---
## Version History
**November 2025:** Complete reference created from official Google documentation
**Updated:** Aligned with GA4 current specifications
**Source:** Google Tag Platform Documentation (developers.google.com/tag-platform)

View File

@@ -0,0 +1,519 @@
# gtag.js Installation Guide
**Version:** GA4 (2025)
**Audience:** Developers implementing direct GA4 tracking
**Prerequisite:** GA4 property and web data stream created
---
## Overview
This guide provides complete step-by-step instructions for installing gtag.js tracking code on websites, including verification, troubleshooting, and platform-specific considerations.
---
## Prerequisites Checklist
Before installation, ensure:
- [ ] GA4 property created in Google Analytics
- [ ] Web data stream configured for your website
- [ ] Measurement ID obtained (format: `G-XXXXXXXXXX`)
- [ ] Access to website HTML source code or header/footer sections
- [ ] Ability to edit `<head>` section of web pages
---
## Step 1: Obtain Measurement ID
### Navigate to GA4 Data Streams
1. Log in to Google Analytics (analytics.google.com)
2. Navigate to **Admin** (gear icon, bottom left)
3. In **Property** column → **Data collection and modification****Data Streams**
4. Click on your **Web** data stream
### Copy Measurement ID
**Location:** Under "Stream details" section
**Format:** `G-XXXXXXXXXX` (10 alphanumeric characters after G-)
**Example:** `G-3K8Y9Z2L4M`
**Note:** Each data stream has a unique Measurement ID. Verify you're copying from the correct stream.
---
## Step 2: Copy Installation Code
### Option A: Copy from GA4 Interface
1. In Data Stream details → Click **"View tag instructions"**
2. Select **"Install manually"**
3. Copy entire code snippet (both `<script>` tags)
### Option B: Use Template Below
```html
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
```
**Replace:** `G-XXXXXXXXXX` with your actual Measurement ID (appears twice)
---
## Step 3: Install Code on Website
### Placement: Critical Rule
**MUST:** Place immediately after opening `<head>` tag
**BEFORE:** All other scripts (except meta tags)
**REASON:** Ensures tracking initializes before any user interactions
### Correct Placement
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My Website</title>
<!-- Google tag (gtag.js) - PLACE HERE -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
<!-- End Google tag -->
<!-- Other scripts below -->
<link rel="stylesheet" href="styles.css">
<script src="other-script.js"></script>
</head>
<body>
<!-- Page content -->
</body>
</html>
```
### Install on All Pages
**Important:** gtag snippet must be on EVERY page you want to track
**Methods:**
- **Template-based sites:** Add to header template/include file
- **WordPress:** Add to theme's `header.php` or use plugin
- **Static sites:** Add to each HTML file
- **React/Vue/Angular:** Add to `index.html` or document head
---
## Step 4: Verification
### Method 1: Google Tag Assistant (Recommended)
**Chrome Extension:**
1. Install "Google Tag Assistant" from Chrome Web Store
2. Visit your website
3. Click extension icon
4. Look for: "Google Analytics: GA4" tag
5. Status should be: "Working" (green checkmark)
### Method 2: GA4 Realtime Reports
**In Google Analytics:**
1. Navigate to **Reports****Realtime**
2. Visit your website in another browser tab
3. Within 30 seconds, should see:
- Active users: 1+
- Event count by Event name: `page_view`, `session_start`, `first_visit`
4. Geographic data showing your location
**If no data:** Wait 2-3 minutes and refresh Realtime report
### Method 3: Browser Developer Console
**Chrome DevTools:**
1. Open website
2. Press F12 (or Cmd+Opt+I on Mac)
3. Go to **Network** tab
4. Filter by "google-analytics"
5. Refresh page
6. Look for requests to `https://www.google-analytics.com/g/collect`
7. Status should be: `204` (success)
**Check Request Payload:**
- Click on collect request
- View **Payload** tab
- Verify `en` (event name) = `page_view`
- Verify `tid` (tracking ID) = your Measurement ID
### Method 4: DebugView (Advanced)
**Enable Debug Mode:**
1. Install "Google Analytics Debugger" Chrome extension
2. Enable extension
3. Visit your website
4. In GA4: **Admin****DebugView**
5. Select your device from dropdown
6. View real-time events with full parameter details
**Events to Verify:**
- `session_start`
- `first_visit` (if first time visiting)
- `page_view`
- `user_engagement`
---
## Step 5: Test Enhanced Measurement (Optional)
If Enhanced Measurement enabled in GA4 Data Streams:
### Test Scroll Tracking
1. Visit long page
2. Scroll to 90% depth
3. Verify `scroll` event in Realtime or DebugView
### Test Outbound Click
1. Click link to external website
2. Verify `click` event with `outbound: true`
### Test File Download
1. Click link to PDF, XLSX, or other file
2. Verify `file_download` event
### Test Form Interaction
1. Interact with form field
2. Verify `form_start` event
3. Submit form
4. Verify `form_submit` event
---
## Platform-Specific Installation
### WordPress
**Method 1: Header/Footer Plugin**
1. Install "Insert Headers and Footers" plugin
2. Go to **Settings****Insert Headers and Footers**
3. Paste gtag snippet in "Scripts in Header" section
4. Save changes
**Method 2: Theme Functions**
1. Edit `functions.php` in child theme
2. Add:
```php
function add_ga4_tracking() {
?>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
<?php
}
add_action('wp_head', 'add_ga4_tracking', 1);
```
**Method 3: GA4 Plugin**
- Install "GA4 WordPress Plugin" or similar
- Follow plugin configuration wizard
### Shopify
**Installation:**
1. Shopify Admin → **Online Store****Themes**
2. Click **Actions****Edit code**
3. Find `theme.liquid` file
4. Locate `<head>` tag
5. Paste gtag snippet immediately after `<head>`
6. Save
**Alternative:**
1. **Settings****Checkout**
2. Scroll to "Additional Scripts"
3. Paste gtag snippet (works only on checkout pages)
### Wix
**Installation:**
1. Wix Editor → Click **Settings** (gear icon)
2. **Tracking & Analytics****+ New Tool** → **Custom**
3. Paste gtag snippet
4. Set to load on "All Pages"
5. Save and publish
### Squarespace
**Installation:**
1. **Settings****Advanced****Code Injection**
2. Paste gtag snippet in **Header** section
3. Save
### React (Create React App)
**Installation:**
1. Edit `public/index.html`
2. Add gtag snippet to `<head>` section
3. For dynamic page tracking:
```javascript
// In App.js or routing component
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function App() {
const location = useLocation();
useEffect(() => {
if (window.gtag) {
window.gtag('event', 'page_view', {
page_path: location.pathname + location.search,
page_title: document.title
});
}
}, [location]);
return (
// Your app
);
}
```
### Next.js
**Installation:**
1. Create `_document.js` in `pages/` directory:
```javascript
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html>
<Head>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX" />
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
`,
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
```
2. For page tracking in `_app.js`:
```javascript
import { useRouter } from 'next/router';
import { useEffect } from 'react';
function MyApp({ Component, pageProps }) {
const router = useRouter();
useEffect(() => {
const handleRouteChange = (url) => {
window.gtag('event', 'page_view', {
page_path: url,
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
return <Component {...pageProps} />;
}
export default MyApp;
```
### Vue.js
**Installation:**
1. Edit `public/index.html`
2. Add gtag snippet to `<head>`
3. For router tracking:
```javascript
// In router/index.js or main.js
import router from './router';
router.afterEach((to, from) => {
if (window.gtag) {
window.gtag('event', 'page_view', {
page_path: to.fullPath,
page_title: to.meta.title || document.title
});
}
});
```
### Angular
**Installation:**
1. Edit `src/index.html`
2. Add gtag snippet to `<head>`
3. Create tracking service:
```typescript
// analytics.service.ts
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
declare let gtag: Function;
@Injectable({
providedIn: 'root'
})
export class AnalyticsService {
constructor(private router: Router) {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe((event: NavigationEnd) => {
gtag('event', 'page_view', {
page_path: event.urlAfterRedirects
});
});
}
}
```
---
## Troubleshooting
### No Data in GA4 Realtime
**Check:**
- [ ] Measurement ID correct (verify both occurrences)
- [ ] Code installed on all pages
- [ ] Code in `<head>` section, before other scripts
- [ ] No JavaScript errors (check browser console)
- [ ] Ad blockers disabled (can block GA4)
- [ ] Browser cache cleared
**Test:**
- Open website in incognito/private browsing
- Check Network tab for requests to google-analytics.com
- Verify status code 204 (success)
### Data Only in DebugView, Not in Reports
**Normal:** This is expected behavior
**Reason:** Standard reports have 24-48 hour processing delay
**Solution:** Use Realtime reports to verify current data
### Duplicate Events
**Cause:** Multiple gtag snippets on page
**Check:**
- Multiple installations in header/footer
- Plugin + manual installation
- Template includes gtag snippet twice
**Solution:** Remove all but one gtag implementation
### Events Firing Multiple Times
**Cause:** Single-page application re-running initialization
**Solution:**
- Initialize gtag only once
- Call `gtag('event')` on route changes, not `gtag('config')`
### Custom Events Not Appearing
**Cause:** Custom parameters not registered as custom dimensions
**Solution:**
1. Admin → Data Display → Custom Definitions
2. Create Custom Dimension
3. Map parameter name exactly (case-sensitive)
4. Wait 24-48 hours for data
---
## Post-Installation Checklist
- [ ] gtag snippet installed in `<head>` of all pages
- [ ] Measurement ID verified and correct
- [ ] Google Tag Assistant shows "Working" status
- [ ] Realtime report shows active users
- [ ] `page_view`, `session_start`, `first_visit` events firing
- [ ] Enhanced Measurement events enabled and verified (if applicable)
- [ ] No JavaScript console errors
- [ ] No duplicate event tracking
- [ ] DebugView accessible and showing events
- [ ] Custom events tested (if implemented)
---
## Next Steps
After successful installation:
1. **Configure Enhanced Measurement**
- Admin → Data Streams → Web Stream → Enhanced measurement
- Enable desired automatic events
2. **Implement Custom Events**
- Add gtag('event') calls for business-specific tracking
- Refer to ga4-recommended-events skill for best practices
3. **Set Up Custom Dimensions**
- Register custom parameters in GA4 Admin
- Wait 24-48 hours for data to populate
4. **Configure Data Retention**
- Admin → Data Settings → Data Retention
- Set to 14 months (recommended)
5. **Review Privacy Settings**
- Implement Consent Mode if required
- Configure data sharing settings
---
## Additional Resources
**GA4 Skills:**
- **ga4-setup** - Initial property configuration
- **ga4-events-fundamentals** - Understanding event structure
- **ga4-recommended-events** - Implementing standard events
- **ga4-debugview** - Advanced testing and validation
**Official Documentation:**
- Google Tag Platform: developers.google.com/tag-platform/gtagjs
- GA4 Setup Guide: developers.google.com/analytics/devguides/collection/ga4
---
**Document Version:** 1.0
**Last Updated:** November 2025
**Maintained By:** GA4 Skills Repository

View File

@@ -0,0 +1,613 @@
# gtag.js Performance Optimization
**Version:** GA4 (2025)
**Focus:** Best practices for high-performance gtag.js implementations
**Impact:** Minimal page load impact, optimal tracking accuracy
---
## Overview
This guide provides comprehensive performance optimization strategies for gtag.js implementations, ensuring minimal impact on page load times while maintaining accurate tracking.
---
## Core Performance Principles
### 1. Async Loading (Built-In)
gtag.js automatically loads asynchronously and does not block page rendering.
**Async Attribute in gtag Snippet:**
```html
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
```
**How It Works:**
- `async` attribute allows script to load in parallel with page content
- Does not block HTML parsing
- Page becomes interactive without waiting for gtag.js
- Script executes as soon as it downloads
**Performance Impact:**
- **Blocking time:** ~0ms (non-blocking)
- **Download time:** ~10-20KB (~50-100ms on 3G)
- **Execution time:** ~5-10ms
**Key Benefit:** Users can interact with page immediately; tracking loads in background
---
### 2. Event Batching
GA4 automatically batches events to reduce network requests.
**How Batching Works:**
- Multiple events sent in single HTTP request
- Batches sent every few seconds or when certain thresholds reached
- Reduces server requests and improves performance
**Automatic Batching:**
```javascript
// These events are automatically batched
gtag('event', 'button_click', { 'button_name': 'Subscribe' });
gtag('event', 'scroll', { 'scroll_depth': 50 });
gtag('event', 'video_progress', { 'video_percent': 25 });
// All sent in single request to GA4
```
**Manual Control Not Required:**
- GA4 handles batching automatically
- No developer intervention needed
- Optimal batch size determined by Google
---
### 3. Script Placement Optimization
**Best Practice:** Place gtag snippet in `<head>` as early as possible
**Rationale:**
- Early initialization captures all user interactions
- Prevents missed events during page load
- Ensures tracking ready before user action
**Optimal Placement:**
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- Preconnect to GA4 domains (optional optimization) -->
<link rel="preconnect" href="https://www.googletagmanager.com">
<link rel="preconnect" href="https://www.google-analytics.com">
<!-- Google tag (gtag.js) - Place here -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>
<!-- Other scripts -->
</head>
<body>
<!-- Content -->
</body>
</html>
```
---
## Advanced Optimization Techniques
### 1. DNS Prefetch & Preconnect
**Purpose:** Reduce DNS lookup and connection time for GA4 domains
**Implementation:**
```html
<head>
<!-- DNS Prefetch (fastest, least resource-intensive) -->
<link rel="dns-prefetch" href="https://www.googletagmanager.com">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
<!-- Preconnect (faster loading, more resource-intensive) -->
<link rel="preconnect" href="https://www.googletagmanager.com">
<link rel="preconnect" href="https://www.google-analytics.com">
<!-- gtag snippet -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
...
</head>
```
**Performance Gain:**
- DNS Prefetch: Saves ~20-120ms on first connection
- Preconnect: Saves ~20-200ms (includes DNS + TCP + TLS)
**When to Use:**
- **DNS Prefetch:** Always safe to use
- **Preconnect:** Use if analytics is critical; uses more resources
---
### 2. Deferred Initialization (Advanced Users Only)
**Scenario:** Delay gtag initialization until user interaction or specific page state
**Use Case:**
- Extremely performance-sensitive pages
- Want to prioritize critical content loading
- Acceptable to miss some initial interactions
**Implementation:**
```html
<head>
<!-- Load gtag.js but don't initialize yet -->
<script>
// Store gtag calls until ready
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
// Flag to prevent premature calls
let gtagReady = false;
// Defer loading gtag.js
window.addEventListener('load', function() {
// Page fully loaded; now load gtag
const script = document.createElement('script');
script.async = true;
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
document.head.appendChild(script);
script.onload = function() {
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
gtagReady = true;
};
});
</script>
</head>
```
**Trade-offs:**
- ✅ Page loads faster (0 blocking)
- ❌ May miss early user interactions
- ❌ More complex implementation
**Recommendation:** Only use if you have extremely tight performance budgets
---
### 3. Conditional Loading
**Scenario:** Load gtag only for specific pages or user conditions
**Example: Load only on non-admin pages**
```javascript
// In your template or script
const isAdminPage = window.location.pathname.startsWith('/admin');
const isInternalIP = false; // Your logic to detect internal traffic
if (!isAdminPage && !isInternalIP) {
// Load gtag
const script = document.createElement('script');
script.async = true;
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
document.head.appendChild(script);
script.onload = function() {
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
};
}
```
**Use Cases:**
- Exclude admin/editor views
- Exclude internal IP addresses
- Load only for specific user segments
---
### 4. Single-Page Application (SPA) Optimization
**Challenge:** SPAs load once but change routes multiple times
**Optimization Strategy:**
1. Initialize gtag once on initial page load
2. Send `page_view` events manually on route changes
3. Avoid re-initializing gtag
**React Example (Optimized):**
```javascript
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function App() {
const location = useLocation();
useEffect(() => {
// Only send page_view, don't re-initialize
if (window.gtag) {
window.gtag('event', 'page_view', {
page_path: location.pathname + location.search,
page_title: document.title
});
}
}, [location]);
return <Router>{/* routes */}</Router>;
}
```
**Performance Impact:**
- ✅ gtag.js loaded once (not on every route)
- ✅ Minimal overhead on route changes (~1-2ms)
- ✅ Accurate page view tracking
---
### 5. Debouncing High-Frequency Events
**Problem:** Tracking every scroll or mouse move creates excessive events
**Solution:** Debounce to limit event frequency
**Debounce Function:**
```javascript
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
```
**Usage with Scroll Tracking:**
```javascript
const trackScrollDebounced = debounce(function() {
const scrollPercent = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
if (scrollPercent >= 50 && !scrollTracked.fifty) {
gtag('event', 'scroll_milestone', {
'scroll_depth': 50
});
scrollTracked.fifty = true;
}
}, 500); // Only fires once every 500ms
window.addEventListener('scroll', trackScrollDebounced);
```
**Performance Gain:**
- Reduces event calls from hundreds to <10
- Minimal CPU overhead
- Same tracking accuracy
---
### 6. Lazy Loading for Secondary Tracking
**Scenario:** Non-critical tracking can be delayed
**Example: Video tracking loaded only when video player visible**
```javascript
// Intersection Observer to detect video visibility
const videoObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Video visible; set up tracking
setupVideoTracking(entry.target);
videoObserver.unobserve(entry.target); // Only set up once
}
});
});
// Observe all video elements
document.querySelectorAll('video').forEach(video => {
videoObserver.observe(video);
});
function setupVideoTracking(video) {
// Add event listeners only when video is visible
video.addEventListener('play', function() {
gtag('event', 'video_start', {
'video_title': this.getAttribute('data-title')
});
});
video.addEventListener('ended', function() {
gtag('event', 'video_complete', {
'video_title': this.getAttribute('data-title')
});
});
}
```
**Benefits:**
- Event listeners added only when needed
- Reduced memory footprint
- No performance impact until video visible
---
## Measurement & Monitoring
### Measuring gtag.js Performance Impact
**1. Chrome DevTools Lighthouse**
```
1. Open website
2. F12 → Lighthouse tab
3. Run Performance audit
4. Check "Third-party code" section
5. Look for google-analytics.com impact
```
**Typical gtag.js Impact:**
- Total Blocking Time: <50ms
- Script Evaluation: 5-10ms
- Network Transfer: ~15-20KB
**2. Network Tab Analysis**
```
1. F12 → Network tab
2. Reload page
3. Filter by "google"
4. Check:
- gtag/js download time
- /g/collect request count
- Total data transferred
```
**3. Performance Tab (Runtime Analysis)**
```
1. F12 → Performance tab
2. Record page load
3. Look for:
- gtag function calls
- Event dispatch timing
- Main thread blocking
```
### Performance Budget
**Recommended Limits:**
- gtag.js file size: <25KB
- Total gtag execution time: <100ms
- Events per page load: <10 (for typical pages)
- Events per minute: <30 (for interactive pages)
**Exceeding Budget:**
- Review event firing frequency
- Check for duplicate gtag snippets
- Audit custom event implementations
---
## Server-Side Optimization
### CDN and Caching
**gtag.js CDN:**
- Hosted on Google's global CDN
- Automatically cached by browsers
- Version updates handled by Google
**Browser Caching:**
- gtag.js cached for ~1 hour
- Subsequent page loads use cached version
- No download required
**First-Party DNS (Advanced):**
- Route analytics requests through your domain
- Improves caching and privacy
- Requires server-side proxy setup
---
## Error Handling for Performance
**Prevent gtag errors from blocking execution:**
```javascript
// Wrap gtag calls in try-catch
function safeGtagEvent(eventName, eventParams) {
try {
if (window.gtag) {
gtag('event', eventName, eventParams);
}
} catch (error) {
console.warn('gtag error:', error);
}
}
// Usage
safeGtagEvent('button_click', {
'button_name': 'Subscribe'
});
```
**Benefits:**
- Prevents JavaScript errors from breaking page
- Gracefully handles gtag not loaded
- Continues execution even if tracking fails
---
## Testing Performance Improvements
### Before/After Comparison
**Metrics to Compare:**
1. **Lighthouse Score** (Performance category)
2. **Total Blocking Time** (TBT)
3. **First Contentful Paint** (FCP)
4. **Time to Interactive** (TTI)
5. **Network requests count**
6. **Total page weight**
**Steps:**
1. Run Lighthouse with current gtag implementation
2. Implement optimization
3. Run Lighthouse again
4. Compare scores
**Example Gains:**
- DNS Prefetch: +2-5 Lighthouse points
- Deferred loading: +5-10 points (may miss events)
- Debouncing: Reduces event count by 80%+
---
## Production Checklist
Before deploying optimized gtag implementation:
- [ ] gtag snippet in `<head>` with `async` attribute
- [ ] DNS prefetch/preconnect added (optional)
- [ ] High-frequency events debounced (scroll, mouse)
- [ ] SPA route changes tracked efficiently
- [ ] Error handling implemented
- [ ] Performance tested with Lighthouse
- [ ] Events verified in DebugView
- [ ] No duplicate gtag snippets
- [ ] Event count reasonable (<30/minute)
- [ ] Total Blocking Time <100ms
---
## Common Performance Issues
### Issue 1: Multiple gtag Snippets
**Problem:** gtag loaded multiple times on same page
**Symptoms:** Duplicate events, slow page load
**Solution:**
```javascript
// Check if gtag already loaded
if (!window.gtag) {
// Load gtag
const script = document.createElement('script');
script.async = true;
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
document.head.appendChild(script);
}
```
### Issue 2: Blocking gtag Calls
**Problem:** gtag calls blocking main thread
**Symptoms:** Janky scrolling, delayed interactions
**Solution:** Wrap in `requestIdleCallback`
```javascript
function trackWhenIdle(eventName, eventParams) {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
gtag('event', eventName, eventParams);
});
} else {
// Fallback for browsers without requestIdleCallback
gtag('event', eventName, eventParams);
}
}
```
### Issue 3: Too Many Events
**Problem:** Hundreds of events fired per page
**Symptoms:** High network usage, poor performance
**Solution:**
- Debounce high-frequency events
- Track milestones, not every action
- Review event necessity
---
## Framework-Specific Optimizations
### React Optimization
**Use useMemo for event data:**
```javascript
import { useMemo } from 'react';
function ProductCard({ product }) {
const eventData = useMemo(() => ({
'item_id': product.id,
'item_name': product.name,
'price': product.price
}), [product.id, product.name, product.price]);
const handleClick = () => {
gtag('event', 'view_item', { 'items': [eventData] });
};
return <div onClick={handleClick}>{product.name}</div>;
}
```
### Vue Optimization
**Use computed properties:**
```vue
<script>
export default {
computed: {
productEventData() {
return {
'item_id': this.product.id,
'item_name': this.product.name,
'price': this.product.price
};
}
},
methods: {
trackView() {
this.$gtag.event('view_item', {
'items': [this.productEventData]
});
}
}
}
</script>
```
---
## Performance Best Practices Summary
**DO:**
- ✅ Use async attribute on gtag script
- ✅ Place snippet in `<head>` early
- ✅ Debounce high-frequency events
- ✅ Use DNS prefetch for GA4 domains
- ✅ Track milestones, not every action
- ✅ Test performance with Lighthouse
**DON'T:**
- ❌ Load gtag synchronously (blocking)
- ❌ Track every scroll or mouse move
- ❌ Load multiple gtag snippets
- ❌ Call gtag in tight loops
- ❌ Ignore performance metrics
- ❌ Send excessive custom parameters
---
**Document Version:** 1.0
**Last Updated:** November 2025
**Performance Impact:** <50ms Total Blocking Time (typical)
**Maintained By:** GA4 Skills Repository

View File

@@ -0,0 +1,821 @@
# Real-World gtag.js Implementation Patterns
**Version:** GA4 (2025)
**Focus:** Production-ready code examples for common tracking scenarios
**Audience:** Developers implementing GA4 tracking
---
## Overview
This document provides battle-tested implementation patterns for common tracking scenarios. All examples are production-ready and include error handling, edge case management, and performance optimizations.
---
## Pattern 1: Form Submission Tracking
### Basic Contact Form
**Scenario:** Track contact form submissions with form details
```html
<form id="contact-form" action="/submit" method="POST">
<input type="email" name="email" id="email" required>
<input type="text" name="company" id="company" required>
<select name="inquiry-type" id="inquiry-type">
<option value="sales">Sales</option>
<option value="support">Support</option>
<option value="other">Other</option>
</select>
<button type="submit">Submit</button>
</form>
<script>
document.getElementById('contact-form').addEventListener('submit', function(e) {
e.preventDefault(); // Prevent immediate submission
// Gather form data
const email = document.getElementById('email').value;
const company = document.getElementById('company').value;
const inquiryType = document.getElementById('inquiry-type').value;
// Extract email domain
const emailDomain = email.split('@')[1];
// Send GA4 event
gtag('event', 'form_submit', {
'form_name': 'Contact Form',
'form_id': 'contact-form',
'form_destination': '/thank-you',
'inquiry_type': inquiryType,
'email_domain': emailDomain, // Not PII - just domain
'company_provided': company ? 'yes' : 'no'
});
// Small delay ensures event is sent
setTimeout(() => {
this.submit();
}, 100);
});
</script>
```
### Multi-Step Form with Progress Tracking
**Scenario:** Track progression through multi-step form
```javascript
// Track form step completion
function trackFormStep(stepNumber, stepName) {
gtag('event', 'form_step_complete', {
'form_name': 'Registration Form',
'step_number': stepNumber,
'step_name': stepName,
'total_steps': 4
});
}
// Track form abandonment
window.addEventListener('beforeunload', function(e) {
const currentStep = getCurrentFormStep(); // Your function
if (currentStep < 4) { // Not completed
gtag('event', 'form_abandoned', {
'form_name': 'Registration Form',
'abandoned_at_step': currentStep,
'total_steps': 4
});
}
});
// Usage
document.getElementById('step1-next').addEventListener('click', function() {
trackFormStep(1, 'Personal Info');
});
document.getElementById('step2-next').addEventListener('click', function() {
trackFormStep(2, 'Company Info');
});
```
---
## Pattern 2: Ecommerce Product Tracking
### Product Page View
**Scenario:** Track product page views with full product details
```javascript
// Get product data from page (common patterns)
const productData = {
id: document.querySelector('[data-product-id]')?.textContent,
name: document.querySelector('.product-title')?.textContent,
price: parseFloat(document.querySelector('.product-price')?.textContent.replace(/[^0-9.]/g, '')),
category: document.querySelector('[data-category]')?.textContent,
brand: document.querySelector('[data-brand]')?.textContent,
variant: document.querySelector('.variant-selected')?.textContent
};
// Validate data exists
if (productData.id && productData.name) {
gtag('event', 'view_item', {
'items': [{
'item_id': productData.id,
'item_name': productData.name,
'item_category': productData.category || 'Unknown',
'item_brand': productData.brand || '',
'item_variant': productData.variant || '',
'price': productData.price || 0,
'quantity': 1
}],
'value': productData.price || 0,
'currency': 'USD'
});
}
```
### Add to Cart Tracking
**Scenario:** Track add to cart with quantity and variant selection
```javascript
function trackAddToCart(productId, productName, quantity, price, variant) {
// Validate required fields
if (!productId || !productName) {
console.warn('Missing required product data for add_to_cart event');
return;
}
gtag('event', 'add_to_cart', {
'items': [{
'item_id': productId,
'item_name': productName,
'item_variant': variant || '',
'price': price || 0,
'quantity': quantity || 1
}],
'value': (price || 0) * (quantity || 1),
'currency': 'USD'
});
}
// Usage with Add to Cart button
document.querySelector('.add-to-cart-btn').addEventListener('click', function() {
const product = getProductDetails(); // Your function to get product data
const quantity = parseInt(document.getElementById('quantity').value) || 1;
trackAddToCart(
product.id,
product.name,
quantity,
product.price,
product.variant
);
});
```
### Complete Purchase Tracking
**Scenario:** Track purchase on order confirmation page
```javascript
// On order confirmation page - get data from page or backend
const orderData = {
transactionId: document.querySelector('[data-transaction-id]')?.textContent,
totalValue: parseFloat(document.querySelector('[data-total]')?.textContent.replace(/[^0-9.]/g, '')),
tax: parseFloat(document.querySelector('[data-tax]')?.textContent.replace(/[^0-9.]/g, '')),
shipping: parseFloat(document.querySelector('[data-shipping]')?.textContent.replace(/[^0-9.]/g, '')),
couponCode: document.querySelector('[data-coupon]')?.textContent || ''
};
// Get purchased items from data attributes or JavaScript object
const purchasedItems = [];
document.querySelectorAll('[data-order-item]').forEach(function(item) {
purchasedItems.push({
'item_id': item.getAttribute('data-item-id'),
'item_name': item.getAttribute('data-item-name'),
'price': parseFloat(item.getAttribute('data-item-price')),
'quantity': parseInt(item.getAttribute('data-item-quantity')),
'item_category': item.getAttribute('data-item-category') || ''
});
});
// Send purchase event (only if transaction ID exists)
if (orderData.transactionId && purchasedItems.length > 0) {
gtag('event', 'purchase', {
'transaction_id': orderData.transactionId,
'affiliation': 'Online Store',
'value': orderData.totalValue,
'currency': 'USD',
'tax': orderData.tax || 0,
'shipping': orderData.shipping || 0,
'coupon': orderData.couponCode,
'items': purchasedItems
});
}
```
### Begin Checkout Tracking
**Scenario:** Track when user starts checkout process
```javascript
// On checkout page load or when checkout button clicked
function trackBeginCheckout(cartItems, cartTotal, coupon) {
const items = cartItems.map(item => ({
'item_id': item.id,
'item_name': item.name,
'price': item.price,
'quantity': item.quantity,
'item_category': item.category || ''
}));
gtag('event', 'begin_checkout', {
'items': items,
'value': cartTotal,
'currency': 'USD',
'coupon': coupon || ''
});
}
// Usage on checkout page load
window.addEventListener('DOMContentLoaded', function() {
const cart = getCartContents(); // Your function
if (cart && cart.items.length > 0) {
trackBeginCheckout(cart.items, cart.total, cart.couponCode);
}
});
```
---
## Pattern 3: User Authentication Tracking
### Login Event
**Scenario:** Track successful user login with method
```javascript
function trackLogin(userId, loginMethod) {
// Set User ID (important for cross-device tracking)
gtag('set', {
'user_id': 'user_' + userId
});
// Send login event
gtag('event', 'login', {
'method': loginMethod // 'email', 'google', 'facebook', etc.
});
}
// Usage after successful authentication
// Example: Email/password login
loginWithEmail(email, password).then(user => {
trackLogin(user.id, 'email');
redirectToAccount();
});
// Example: Google OAuth
signInWithGoogle().then(user => {
trackLogin(user.id, 'google');
redirectToAccount();
});
```
### Sign Up Event
**Scenario:** Track new user registration
```javascript
function trackSignUp(userId, signUpMethod, userTier) {
// Set User ID
gtag('set', {
'user_id': 'user_' + userId
});
// Set user properties
gtag('set', {
'subscription_tier': userTier || 'free',
'signup_date': new Date().toISOString().split('T')[0]
});
// Send sign_up event
gtag('event', 'sign_up', {
'method': signUpMethod // 'email', 'social'
});
}
// Usage after successful registration
registerUser(userData).then(user => {
trackSignUp(user.id, 'email', user.tier);
showWelcomeMessage();
});
```
### Logout Event
**Scenario:** Clear User ID and track logout
```javascript
function trackLogout() {
// Send custom logout event
gtag('event', 'logout');
// CRITICAL: Clear User ID with null (not empty string)
gtag('set', {
'user_id': null
});
// Also clear any user properties
gtag('set', {
'subscription_tier': null,
'customer_segment': null
});
}
// Usage on logout button click
document.getElementById('logout-btn').addEventListener('click', function() {
trackLogout();
performLogout(); // Your logout function
});
```
---
## Pattern 4: Single-Page Application (SPA) Tracking
### React Router Page Tracking
**Scenario:** Track page views in React SPA
```javascript
// In App.js or routing component
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function App() {
const location = useLocation();
useEffect(() => {
// Send page_view on route change
if (window.gtag) {
window.gtag('event', 'page_view', {
page_path: location.pathname + location.search,
page_title: document.title
});
}
}, [location]);
return (
<Router>
{/* Your routes */}
</Router>
);
}
```
### Vue Router Page Tracking
**Scenario:** Track page views in Vue SPA
```javascript
// In router/index.js
import router from './router';
router.afterEach((to, from) => {
// Send page_view after navigation
if (window.gtag) {
window.gtag('event', 'page_view', {
page_path: to.fullPath,
page_title: to.meta.title || document.title,
page_location: window.location.href
});
}
});
```
### Virtual Page View Pattern
**Scenario:** Track modal/overlay views as virtual pages
```javascript
function trackVirtualPageView(virtualPath, virtualTitle) {
gtag('event', 'page_view', {
page_path: virtualPath,
page_title: virtualTitle,
page_location: window.location.href + virtualPath
});
}
// Usage when opening modal
function openPricingModal() {
showModal('pricing-modal'); // Your function
trackVirtualPageView('/virtual/pricing-modal', 'Pricing Details');
}
// Usage when changing tab
document.querySelectorAll('.tab-link').forEach(tab => {
tab.addEventListener('click', function() {
const tabName = this.getAttribute('data-tab');
trackVirtualPageView('/virtual/tab-' + tabName, 'Tab: ' + tabName);
});
});
```
---
## Pattern 5: Video Engagement Tracking
### YouTube Video Tracking
**Scenario:** Track YouTube video engagement (start, progress, complete)
```javascript
// For embedded YouTube videos with JS API
let player;
let videoTracked = {
started: false,
progress25: false,
progress50: false,
progress75: false,
completed: false
};
function onYouTubeIframeAPIReady() {
player = new YT.Player('youtube-player', {
videoId: 'VIDEO_ID',
events: {
'onStateChange': onPlayerStateChange
}
});
}
function onPlayerStateChange(event) {
const videoData = player.getVideoData();
const videoTitle = videoData.title;
const videoDuration = player.getDuration();
// Video started
if (event.data === YT.PlayerState.PLAYING && !videoTracked.started) {
gtag('event', 'video_start', {
'video_title': videoTitle,
'video_provider': 'youtube',
'video_duration': Math.round(videoDuration)
});
videoTracked.started = true;
// Set up progress tracking
startProgressTracking();
}
// Video ended
if (event.data === YT.PlayerState.ENDED && !videoTracked.completed) {
gtag('event', 'video_complete', {
'video_title': videoTitle,
'video_provider': 'youtube',
'video_percent': 100
});
videoTracked.completed = true;
}
}
function startProgressTracking() {
const checkProgress = setInterval(() => {
if (!player || player.getPlayerState() !== YT.PlayerState.PLAYING) {
clearInterval(checkProgress);
return;
}
const currentTime = player.getCurrentTime();
const duration = player.getDuration();
const percent = (currentTime / duration) * 100;
const videoTitle = player.getVideoData().title;
// Track 25% milestone
if (percent >= 25 && !videoTracked.progress25) {
gtag('event', 'video_progress', {
'video_title': videoTitle,
'video_provider': 'youtube',
'video_percent': 25
});
videoTracked.progress25 = true;
}
// Track 50% milestone
if (percent >= 50 && !videoTracked.progress50) {
gtag('event', 'video_progress', {
'video_title': videoTitle,
'video_provider': 'youtube',
'video_percent': 50
});
videoTracked.progress50 = true;
}
// Track 75% milestone
if (percent >= 75 && !videoTracked.progress75) {
gtag('event', 'video_progress', {
'video_title': videoTitle,
'video_provider': 'youtube',
'video_percent': 75
});
videoTracked.progress75 = true;
}
}, 1000);
}
```
### HTML5 Video Tracking
**Scenario:** Track native HTML5 video engagement
```javascript
const video = document.getElementById('my-video');
const videoTracked = {
started: false,
progress25: false,
progress50: false,
progress75: false,
completed: false
};
// Video started
video.addEventListener('play', function() {
if (!videoTracked.started) {
gtag('event', 'video_start', {
'video_title': this.getAttribute('data-video-title') || 'Untitled Video',
'video_provider': 'html5',
'video_duration': Math.round(this.duration)
});
videoTracked.started = true;
}
});
// Track progress
video.addEventListener('timeupdate', function() {
const percent = (this.currentTime / this.duration) * 100;
const videoTitle = this.getAttribute('data-video-title') || 'Untitled Video';
if (percent >= 25 && !videoTracked.progress25) {
gtag('event', 'video_progress', {
'video_title': videoTitle,
'video_provider': 'html5',
'video_percent': 25
});
videoTracked.progress25 = true;
}
if (percent >= 50 && !videoTracked.progress50) {
gtag('event', 'video_progress', {
'video_title': videoTitle,
'video_provider': 'html5',
'video_percent': 50
});
videoTracked.progress50 = true;
}
if (percent >= 75 && !videoTracked.progress75) {
gtag('event', 'video_progress', {
'video_title': videoTitle,
'video_provider': 'html5',
'video_percent': 75
});
videoTracked.progress75 = true;
}
});
// Video completed
video.addEventListener('ended', function() {
if (!videoTracked.completed) {
gtag('event', 'video_complete', {
'video_title': this.getAttribute('data-video-title') || 'Untitled Video',
'video_provider': 'html5',
'video_percent': 100
});
videoTracked.completed = true;
}
});
```
---
## Pattern 6: Search Tracking
### Site Search Tracking
**Scenario:** Track internal site search queries and results
```javascript
// Track search when performed
function trackSearch(searchTerm, numberOfResults) {
gtag('event', 'search', {
'search_term': searchTerm,
'number_of_results': numberOfResults || 0
});
}
// Usage with search form
document.getElementById('search-form').addEventListener('submit', function(e) {
e.preventDefault();
const searchTerm = document.getElementById('search-input').value;
performSearch(searchTerm).then(results => {
trackSearch(searchTerm, results.length);
displayResults(results);
});
});
// Track search result click
function trackSearchResultClick(searchTerm, resultPosition, resultTitle) {
gtag('event', 'select_content', {
'content_type': 'search_result',
'search_term': searchTerm,
'content_position': resultPosition,
'content_title': resultTitle
});
}
// Usage
document.querySelectorAll('.search-result').forEach((result, index) => {
result.addEventListener('click', function() {
const searchTerm = document.getElementById('search-input').value;
const resultTitle = this.querySelector('.result-title').textContent;
trackSearchResultClick(searchTerm, index + 1, resultTitle);
});
});
```
---
## Pattern 7: Engagement Tracking
### Scroll Depth Tracking (Custom)
**Scenario:** Track custom scroll milestones beyond Enhanced Measurement
```javascript
const scrollTracked = {
25: false,
50: false,
75: false,
90: false
};
window.addEventListener('scroll', debounce(function() {
const scrollPercent = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
Object.keys(scrollTracked).forEach(threshold => {
if (scrollPercent >= parseInt(threshold) && !scrollTracked[threshold]) {
gtag('event', 'scroll_milestone', {
'scroll_depth': parseInt(threshold),
'page_path': window.location.pathname
});
scrollTracked[threshold] = true;
}
});
}, 500));
// Debounce function to limit event firing
function debounce(func, wait) {
let timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, arguments), wait);
};
}
```
### Time on Page Tracking
**Scenario:** Track engaged time on page
```javascript
let engagementStartTime = Date.now();
let totalEngagementTime = 0;
let isEngaged = true;
// Track engagement time when user leaves page
window.addEventListener('beforeunload', function() {
if (isEngaged) {
totalEngagementTime += Date.now() - engagementStartTime;
}
const timeOnPage = Math.round(totalEngagementTime / 1000); // Convert to seconds
if (timeOnPage > 0) {
gtag('event', 'page_engagement', {
'engagement_time': timeOnPage,
'page_path': window.location.pathname
});
}
});
// Pause tracking when page not visible
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
if (isEngaged) {
totalEngagementTime += Date.now() - engagementStartTime;
isEngaged = false;
}
} else {
isEngaged = true;
engagementStartTime = Date.now();
}
});
```
---
## Pattern 8: Error Tracking
### JavaScript Error Tracking
**Scenario:** Track client-side JavaScript errors
```javascript
window.addEventListener('error', function(event) {
gtag('event', 'exception', {
'description': event.message,
'fatal': false,
'error_location': event.filename + ':' + event.lineno + ':' + event.colno
});
});
// Track unhandled promise rejections
window.addEventListener('unhandledrejection', function(event) {
gtag('event', 'exception', {
'description': 'Unhandled Promise: ' + event.reason,
'fatal': false
});
});
```
---
## Best Practices for All Patterns
**1. Validate Data Before Tracking**
```javascript
if (productId && productName) {
gtag('event', 'view_item', { ... });
} else {
console.warn('Missing required product data');
}
```
**2. Handle Async Operations**
```javascript
// For operations that redirect/navigate
setTimeout(() => {
window.location.href = '/redirect';
}, 100);
```
**3. Debounce High-Frequency Events**
```javascript
const trackScrollDebounced = debounce(() => {
gtag('event', 'scroll_milestone', { ... });
}, 500);
```
**4. Use Consistent Naming**
```javascript
// Good: snake_case, descriptive
'form_submit', 'video_complete', 'product_view'
// Bad: inconsistent, unclear
'formSubmit', 'videoEnd', 'pv'
```
**5. Include Context Parameters**
```javascript
gtag('event', 'button_click', {
'button_name': 'Subscribe',
'button_location': 'header', // Context
'user_authenticated': isLoggedIn() ? 'yes' : 'no' // Context
});
```
---
## Common Pitfalls to Avoid
**Calling gtag() before initialization**
✅ Always check `if (window.gtag)` before calling
**Using empty string to clear User ID**
✅ Always use `null`: `gtag('set', {'user_id': null})`
**Sending PII in parameters**
✅ Extract domain from email, use hashed IDs
**Not handling errors**
✅ Wrap in try-catch, validate data
**Tracking every scroll or mouse move**
✅ Debounce, use meaningful milestones
---
**Document Version:** 1.0
**Last Updated:** November 2025
**Maintained By:** GA4 Skills Repository