Initial commit
This commit is contained in:
@@ -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)
|
||||
519
skills/ga4-gtag-implementation/references/installation-guide.md
Normal file
519
skills/ga4-gtag-implementation/references/installation-guide.md
Normal 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
|
||||
@@ -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
|
||||
821
skills/ga4-gtag-implementation/references/real-world-patterns.md
Normal file
821
skills/ga4-gtag-implementation/references/real-world-patterns.md
Normal 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
|
||||
Reference in New Issue
Block a user