Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:32:45 +08:00
commit c02dafae1d
41 changed files with 17283 additions and 0 deletions

View File

@@ -0,0 +1,572 @@
# Data Layer Best Practices
## Overview
The data layer is a JavaScript object used by Google Tag Manager and gtag.js to pass information to tags. Events or variables can be passed via the data layer, and triggers can be set up based on the values of variables. This guide covers best practices for implementing and maintaining the data layer.
## Installation and Setup
### Proper Data Layer Initialization
The data layer must be established before the Tag Manager or Google tag snippet loads:
```html
<script>
window.dataLayer = window.dataLayer || [];
</script>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->
```
### Gtag.js Installation
For gtag.js implementations:
```html
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments)};
gtag('js', new Date());
gtag('config', 'TAG_ID');
</script>
```
## How Data Layer Information is Processed
Tag Manager processes data layer messages on a first-in, first-out (FIFO) basis:
1. Each message is processed one at a time, in the order received
2. If a message is an event, any tags with trigger conditions that have been met will fire before Tag Manager moves to the next message
3. `gtag()` or `dataLayer.push()` calls queue messages for processing after all pending messages
4. Updated data layer values are not guaranteed to be available for the next event
### Event Handling Pattern
To ensure data availability, add an event name to messages and listen for that event with a Custom Event trigger:
```javascript
// Push data with a custom event
dataLayer.push({
'event': 'userData',
'userId': '12345',
'userType': 'premium'
});
```
## Best Practices
### 1. Never Overwrite the dataLayer Variable
**Bad:**
```javascript
// This overwrites existing values!
dataLayer = [{'item': 'value'}];
```
**Good:**
```javascript
// Initialize at the top of the page
window.dataLayer = window.dataLayer || [];
// Then use push() to add values
dataLayer.push({'item': 'value'});
```
### 2. Use Correct Casing
The `dataLayer` object name is case-sensitive:
**Bad:**
```javascript
datalayer.push({'pageTitle': 'Home'}); // lowercase 'l'
DataLayer.push({'pageTitle': 'Home'}); // capital 'D'
```
**Good:**
```javascript
dataLayer.push({'pageTitle': 'Home'}); // correct camelCase
```
### 3. Use Proper Quote Marks
All data layer variable names should be enclosed in quotes:
**Bad:**
```javascript
dataLayer.push({new-variable: 'value'}); // No quotes
dataLayer.push({newVariable: 'value'}); // No quotes (though valid JS)
```
**Good:**
```javascript
dataLayer.push({'new-variable': 'value'}); // Proper quotes
dataLayer.push({'newVariable': 'value'}); // Proper quotes
```
### 4. Keep Variable Names Consistent Across Pages
Use consistent naming conventions for the same concept across all pages:
**Bad:**
```javascript
// Homepage:
dataLayer.push({'visitorType': 'low-value'});
// Checkout Page:
dataLayer.push({'visitor_type': 'high-value'}); // Different naming!
```
**Good:**
```javascript
// Homepage:
dataLayer.push({'visitorType': 'low-value'});
// Checkout Page:
dataLayer.push({'visitorType': 'high-value'}); // Consistent naming
```
## Using the Data Layer with Event Handlers
### Sending Events
Use the `event` key to initiate sending events:
```javascript
dataLayer.push({'event': 'event_name'});
```
### Event with Button Click
```html
<button onclick="dataLayer.push({'event': 'login'});">Login</button>
```
### Dynamic Data Layer Variables
Push variables dynamically to capture information:
```javascript
dataLayer.push({'variable_name': 'variable_value'});
```
#### Example: Form Value Capture
```javascript
// Capture color selection
dataLayer.push({'color': 'red'});
```
## Advanced Patterns
### Pushing Multiple Variables and Events
You can push multiple variables and events simultaneously:
```javascript
dataLayer.push({
'color': 'red',
'conversionValue': 50,
'event': 'customize'
});
```
### Persisting Data Layer Variables Across Pages
To persist data layer variables between pages:
1. Call `dataLayer.push()` on each page load after data layer instantiation
2. Place the push above the GTM container code for immediate availability
```html
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'Pageview',
'pagePath': 'https://www.example.com/products',
'pageTitle': 'Product Catalog',
'visitorType': 'customer'
});
</script>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->
```
**Important:** Variables persist only as long as the visitor remains on the current page. Variables relevant across pages must be declared in the data layer on each page.
## Custom Data Layer Methods
### The set() Method
Set values to retrieve later:
```javascript
window.dataLayer.push(function() {
this.set('time', new Date());
});
```
### The get() Method
Retrieve values that were set:
```javascript
window.dataLayer.push(function() {
const existingTime = this.get('time');
if (existingTime !== null) {
// Value exists, use it
} else {
// Value doesn't exist
}
});
```
### The reset() Method
Reset the data in the data layer (useful for single-page applications):
```javascript
window.dataLayer.push(function() {
this.reset();
});
```
## Renaming the Data Layer
### For gtag.js
Add a query parameter named "l" to set a new data layer name:
```html
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID&l=myNewName"></script>
<script>
window.myNewName = window.myNewName || [];
function gtag(){myNewName.push(arguments);}
gtag('js', new Date());
gtag('config', 'TAG_ID');
</script>
```
### For Tag Manager
Replace the data layer parameter value in the container snippet:
```html
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','myNewName','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->
```
Update all references to match the new name:
```javascript
<script>
myNewName = window.myNewName || [];
myNewName.push({'variable_name': 'variable_value'});
</script>
```
## Common Data Layer Patterns
### Page View Data
```javascript
dataLayer.push({
'event': 'pageview',
'page': {
'path': '/products/shoes',
'title': 'Shoes | My Store',
'category': 'Products'
},
'user': {
'id': 'USER123',
'status': 'logged_in',
'type': 'premium'
}
});
```
### User Interaction
```javascript
dataLayer.push({
'event': 'button_click',
'element': {
'id': 'cta-button',
'text': 'Get Started',
'destination': '/signup'
}
});
```
### Form Submission
```javascript
dataLayer.push({
'event': 'form_submission',
'form': {
'id': 'contact-form',
'name': 'Contact Us',
'fields': 5
}
});
```
### Video Tracking
```javascript
dataLayer.push({
'event': 'video_start',
'video': {
'title': 'Product Demo',
'duration': 120,
'provider': 'youtube',
'url': 'https://youtube.com/watch?v=xxx'
}
});
```
## Troubleshooting
### Common Errors
#### 1. Overwriting dataLayer
**Problem:** Using direct assignment instead of push
```javascript
// Wrong
dataLayer = [{'item': 'value'}];
```
**Solution:** Always use push after initialization
```javascript
// Correct
dataLayer.push({'item': 'value'});
```
#### 2. Case Sensitivity
**Problem:** Incorrect casing
```javascript
datalayer.push({'pageTitle': 'Home'}); // Wrong
```
**Solution:** Use correct camelCase
```javascript
dataLayer.push({'pageTitle': 'Home'}); // Correct
```
#### 3. Missing Quotes
**Problem:** Variable names without quotes
```javascript
dataLayer.push({pageTitle: 'Home'}); // Can cause issues
```
**Solution:** Always use quotes
```javascript
dataLayer.push({'pageTitle': 'Home'}); // Better
```
#### 4. Inconsistent Variable Names
**Problem:** Using different names for the same concept
```javascript
// Page 1
dataLayer.push({'user_type': 'premium'});
// Page 2
dataLayer.push({'userType': 'premium'});
```
**Solution:** Document and enforce naming conventions
```javascript
// All pages
dataLayer.push({'userType': 'premium'});
```
## Data Layer Structure Best Practices
### Use Nested Objects for Organization
```javascript
dataLayer.push({
'event': 'purchase',
'transaction': {
'id': 'T12345',
'revenue': 99.99,
'tax': 9.99,
'shipping': 5.00
},
'customer': {
'id': 'C67890',
'type': 'returning',
'lifetime_value': 1500.00
}
});
```
### Use Arrays for Multiple Similar Items
```javascript
dataLayer.push({
'event': 'product_impressions',
'products': [
{'id': 'P1', 'name': 'Blue Shoes', 'price': 49.99},
{'id': 'P2', 'name': 'Red Shoes', 'price': 59.99},
{'id': 'P3', 'name': 'Green Shoes', 'price': 54.99}
]
});
```
### Use Clear, Descriptive Names
**Bad:**
```javascript
dataLayer.push({
'e': 'clk',
'v': '100',
't': 'btn'
});
```
**Good:**
```javascript
dataLayer.push({
'event': 'button_click',
'value': '100',
'element_type': 'button'
});
```
## Testing and Validation
### 1. Use Debug Mode
Enable debug mode in Tag Manager to verify data layer pushes in real-time.
### 2. Console Logging
Check data layer contents in the browser console:
```javascript
console.log(window.dataLayer);
```
### 3. Data Layer Checker Extensions
Use browser extensions like:
- Google Tag Assistant
- dataLayer Checker
- Tag Manager/Analytics Debugger
### 4. Preview Mode
Always test changes in Tag Manager Preview mode before publishing.
## Performance Considerations
### 1. Push Data Before It's Needed
Push data layer variables before the tags that need them fire.
### 2. Avoid Excessive Pushes
Consolidate data into single pushes when possible:
**Bad:**
```javascript
dataLayer.push({'userId': '123'});
dataLayer.push({'userType': 'premium'});
dataLayer.push({'event': 'user_data'});
```
**Good:**
```javascript
dataLayer.push({
'userId': '123',
'userType': 'premium',
'event': 'user_data'
});
```
### 3. Don't Push Unnecessarily Large Data
Avoid pushing very large arrays or objects that won't be used.
### 4. Clear Data for SPAs
For single-page applications, reset the data layer between virtual page views:
```javascript
window.dataLayer.push(function() {
this.reset();
});
```
## Documentation
### Maintain a Data Layer Specification
Document all data layer variables:
- Variable name
- Data type
- When it's populated
- Example values
- Pages where it appears
- Tags/triggers that use it
### Example Documentation Format
```markdown
## userType
- **Type:** String
- **Values:** 'guest', 'registered', 'premium'
- **Populated:** On all pages after user identification
- **Example:** `{'userType': 'premium'}`
- **Used by:** User Segmentation Tags, Personalization Triggers
```
## Resources
- [Google Tag Manager Data Layer Documentation](https://developers.google.com/tag-platform/tag-manager/datalayer)
- [Data Layer Best Practices (Google Support)](https://support.google.com/tagmanager/answer/6164391)
- [GTM Help Center](https://support.google.com/tagmanager)

View File

@@ -0,0 +1,258 @@
# The Data Layer
**Source**: https://developers.google.com/tag-platform/tag-manager/datalayer
**Extracted**: 2025-01-09
The data layer is an object used by Google Tag Manager and gtag.js to pass information to tags. Events or variables can be passed via the data layer, and triggers can be set up based on the values of variables.
For example, if you fire a remarketing tag when the value of `purchase_total` is greater than $100, or based on the specific events, e.g. when a button is clicked, your data layer can be configured to make that data available to your tags. The data layer object is structured as JSON. For example:
```json
{
"event": "checkout_button",
"gtm": {
"uniqueEventId": 2,
"start": 1639524976560,
"scrollThreshold": 90,
"scrollUnits": "percent",
"scrollDirection": "vertical",
"triggers": "1_27"
},
"value": "120"
}
```
Google tags are designed to easily reference information that is added to the data layer in an organized and predictable way, rather than by parsing variables, transaction information, page categories, and other signals scattered throughout your page. A data layer implementation populated with variables and associated values will help to ensure that relevant data is available when your tags need them.
## Installation
For Tag Manager web page installations, you must create a data layer. The data layer is established before Tag Manager is loaded:
```javascript
<script>
window.dataLayer = window.dataLayer || [];
</script>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->
```
In standard gtag.js implementations where the tag has been copied from within the product and added to a web page, the code to establish the data layer is provided. In custom implementations of the Google tag, add the data layer code at the beginning of your script:
```javascript
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments)};
gtag('js', new Date());
gtag('config', 'TAG_ID');
</script>
```
## How Data Layer Information is Processed
When a container is loaded, Tag Manager will begin to process all queued data layer push messages. Tag Manager processes messages on a first-in, first-out basis: Each message is processed one at a time, in the order it was received. If the message is an event, any tags with trigger conditions that have been met will fire before Tag Manager moves on to the next message.
If a `gtag()` or `dataLayer.push()` call is made by code on a page, in a Custom Template, or in a Custom HTML tag, the associated message is queued and processed after all other pending messages are evaluated. This means that any updated data layer values are not guaranteed to be available for the next event. To handle these cases, you should add an event name to a message as it is pushed to the data layer, and then listen for that event name with a Custom Event trigger.
## Use a Data Layer with Event Handlers
The `dataLayer` object uses an `event` command to initiate the sending of events.
The Google tag and Tag Manager use a special data layer variable called `event` that is used by JavaScript event listeners to fire tags when a user interacts with website elements. For example, you may want to fire a conversion tracking tag when a user clicks a purchase confirmation button. Events may be called whenever a user interacts with website elements such as links, buttons, scrolls, etc.
This functionality is accomplished by calling `dataLayer.push()` when an event occurs. The syntax for sending an event with `dataLayer.push()` is as follows:
```javascript
dataLayer.push({'event': 'event_name'});
```
Where `event_name` is a string that describes the event, such as `'login'`, `purchase`, or `search`.
Use `dataLayer.push()` to send event data when an action occurs that you'd like to measure. For example, to send an event when a user clicks a button, modify the button's `onclick` handler:
```html
<button onclick="dataLayer.push({'event': 'login'});">Button 1</button>
```
You can push data layer variables to the data layer dynamically to capture information such as values entered or selected in a form, metadata associated with a video that the visitor is playing, the color of a product (e.g. a car) customized by the visitor, the destination URLs of clicked links, etc.
The basic syntax for setting dynamic data layer variables is as follows:
```javascript
dataLayer.push({'variable_name': 'variable_value'});
```
Where `'variable_name'` is a string indicating the name of the data layer variable to be set, and `'variable_value'` is a string indicating the value of the data layer variable to be set or replaced.
For example, to set a data layer variable with a color preference when a visitor engages with a product customization tool:
```javascript
dataLayer.push({'color': 'red'});
```
## One Push, Multiple Variables
You can push multiple variables and events at once:
```javascript
dataLayer.push({
'color': 'red',
'conversionValue': 50,
'event': 'customize'
});
```
## Persist Data Layer Variables
To persist data layer variables between web pages, call `dataLayer.push()` after the data layer has been instantiated on each page load, and push the variables to the data layer. If you want these data layer variables to be available to Tag Manager when the container is loaded, add a `dataLayer.push()` call above the Tag Manager container code:
```javascript
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
'event': 'Pageview',
'pagePath': 'https://www.googleanalytics.dev/pancakes',
'pageTitle': 'Pancake Event Signup',
'visitorType': 'customer'
});
</script>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->
```
Each variable declared within the data layer object will persist only as long as the visitor remains on the current page. Data layer variables that are relevant across pages (e.g. `visitorType`) must be declared in the data layer on each page of your website. While you don't need to put the same set of variables in the data layer on every page, you should use a consistent naming convention. In other words: if you set the page category on the signup page using a variable called `pageCategory`, your product and purchase pages should use the `pageCategory` variable as well.
## Troubleshooting
Here are some data layer troubleshooting tips:
**Do not overwrite the `window.dataLayer` variable:** When you use the data layer directly (e.g. `dataLayer = [{'item': 'value'}]`), it will overwrite any existing values in the `dataLayer`. Tag Manager installations should instantiate the data layer as high up in the source code as possible, above the container snippet, using `window.dataLayer = window.dataLayer || [];`. After the `dataLayer` has been declared, use `dataLayer.push({'item': 'value'})` to add additional values to it, and if those values need to be available to Tag Manager when the page loads, then that `dataLayer.push()` call needs to be above the Tag Manager container code as well.
**The `dataLayer` object name is case-sensitive:** If you try to push a variable or event without the proper casing, the push will not work.
```javascript
datalayer.push({'pageTitle': 'Home'}); // Bad (datalayer in lowercase)
dataLayer.push({'pageTitle': 'Home'}); // Good (dataLayer in camel case)
```
`dataLayer.push` must be called with valid JavaScript objects. All data layer variable names should be enclosed in quotes.
```javascript
dataLayer.push({new-variable: 'value'}); // Bad - no quote marks
dataLayer.push({'new-variable': 'value'}); // Good - proper quote marks
```
**Keep variable names consistent across pages:** If you use different variable names for the same concept on different pages, your tags will be unable to consistently fire in all desired locations.
Bad:
```javascript
// Homepage:
dataLayer.push({'visitorType': 'low-value'});
// Checkout Page:
dataLayer.push({'visitor_type': 'high-value'});
```
Good:
```javascript
// Homepage:
dataLayer.push({'visitorType': 'low-value'});
// Checkout Page:
dataLayer.push({'visitorType': 'high-value'});
```
## Rename the Data Layer
The default name of the data layer object initiated by the Google tag or Tag Manager is `dataLayer`. If you'd prefer to use a different name for your data layer, you may do so by editing the data layer parameter value in your Google tag or Tag Manager container snippet with the name of your choice.
### For gtag.js
Add a query parameter named "l" to the URL to set the new data layer name, e.g. `l=myNewName`. Update all instances of `dataLayer` in the Google tag snippet to the new name:
```javascript
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=TAG_ID&l=myNewName"></script>
<script>
window.myNewName = window.myNewName || [];
function gtag(){myNewName.push(arguments);}
gtag('js', new Date());
gtag('config', 'TAG_ID');
</script>
```
### For Tag Manager
Replace the data layer parameter value in your container snippet with the name of your choice:
```javascript
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','myNewName','GTM-XXXXXX');</script>
<!-- End Google Tag Manager -->
```
Once renamed, all references to your data layer (i.e. when declaring the data layer above the snippet, or when pushing events or dynamic data layer variables to the data layer with the `.push()` command) must be adjusted to reflect your custom data layer name:
```javascript
<script>
myNewName = window.dataLayer || [];
myNewName.push({'variable_name': 'variable_value'});
</script>
```
## Custom Data Layer Methods
If you push a function to the data layer, it will be invoked with this set to an abstract data model. This abstract data model can get and set values to a key value store, and also provides a way to reset the data layer.
### Set
The `set` function on the abstract data model lets you set values to retrieve through get:
```javascript
window.dataLayer.push(function() {
this.set('time', new Date());
});
```
### Get
The `get` function on the abstract data model lets you retrieve values that were set:
```javascript
window.dataLayer.push(function() {
const existingTime = this.get('time');
if (existingTime !== null) {
// Change behavior based on whether or not this value exists...
} else {
// ...
}
})
```
### Reset
The `reset` function on the abstract data model lets you reset the data in the data layer. This is most useful with a page that will remain open and the data layer size continues to grow over time:
```javascript
window.dataLayer.push(function() {
this.reset();
})
```

View File

@@ -0,0 +1,542 @@
# E-commerce Data Layer Patterns
## Overview
Ecommerce events in Google Analytics 4 (GA4) enable you to collect information about shopping behavior and quantify your most popular products, measure the influence of promotions and product placement on revenue.
## E-commerce Object Structure
### GA4 E-commerce Schema
All ecommerce events use an `items` array to represent products and services. The items array can include up to 200 elements and supports up to 27 custom parameters in addition to prescribed parameters.
#### Item Object Structure
```javascript
{
item_id: "SKU_12345", // Required
item_name: "Stan and Friends Tee", // Required
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_category4: "Crew",
item_category5: "Short sleeve",
item_list_id: "related_products",
item_list_name: "Related Products",
item_variant: "green",
location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo",
price: 10.01,
quantity: 3
}
```
## E-commerce Events
### 1. View Item List
When a user is presented with a list of results (e.g., search results, category page), send a `view_item_list` event:
```javascript
gtag("event", "view_item_list", {
item_list_id: "related_products",
item_list_name: "Related products",
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_category4: "Crew",
item_category5: "Short sleeve",
item_list_id: "related_products",
item_list_name: "Related Products",
item_variant: "green",
location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo",
price: 10.01,
quantity: 3
}
]
});
```
### 2. Select Item
When a user selects an item from the list:
```javascript
gtag("event", "select_item", {
item_list_id: "related_products",
item_list_name: "Related products",
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
// ... other item parameters
}
]
});
```
### 3. View Item Details
Measure how many times item details are viewed:
```javascript
gtag("event", "view_item", {
currency: "USD",
value: 30.03,
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
price: 10.01,
quantity: 3
// ... other item parameters
}
]
});
```
### 4. Add to Cart
Measure when an item is added to a shopping cart:
```javascript
gtag("event", "add_to_cart", {
currency: "USD",
value: 30.03,
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
price: 10.01,
quantity: 3
// ... other item parameters
}
]
});
```
### 5. Remove from Cart
Measure when a user removes an item from cart:
```javascript
gtag("event", "remove_from_cart", {
currency: "USD",
value: 30.03,
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
price: 10.01,
quantity: 3
// ... other item parameters
}
]
});
```
### 6. View Cart
When a user views the cart:
```javascript
gtag("event", "view_cart", {
currency: "USD",
value: 30.03,
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
price: 10.01,
quantity: 3
// ... other item parameters
}
]
});
```
### 7. Add to Wishlist
Measure when an item is added to a wishlist:
```javascript
gtag("event", "add_to_wishlist", {
currency: "USD",
value: 30.03,
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
price: 10.01,
quantity: 3
// ... other item parameters
}
]
});
```
### 8. Begin Checkout
Measure the first step in a checkout process:
```javascript
gtag("event", "begin_checkout", {
currency: "USD",
value: 30.03,
coupon: "SUMMER_FUN",
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
price: 10.01,
quantity: 3
// ... other item parameters
}
]
});
```
### 9. Add Shipping Info
When a user adds shipping information:
```javascript
gtag("event", "add_shipping_info", {
currency: "USD",
value: 30.03,
coupon: "SUMMER_FUN",
shipping_tier: "Ground",
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
price: 10.01,
quantity: 3
// ... other item parameters
}
]
});
```
### 10. Add Payment Info
When a user submits payment information:
```javascript
gtag("event", "add_payment_info", {
currency: "USD",
value: 30.03,
coupon: "SUMMER_FUN",
payment_type: "Credit Card",
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
price: 10.01,
quantity: 3
// ... other item parameters
}
]
});
```
### 11. Purchase
Measure a purchase:
```javascript
gtag("event", "purchase", {
transaction_id: "T_12345", // Required
value: 72.05, // Sum of (price * quantity) for all items
tax: 3.60,
shipping: 5.99,
currency: "USD",
coupon: "SUMMER_SALE",
customer_type: "new",
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_category4: "Crew",
item_category5: "Short sleeve",
item_list_id: "related_products",
item_list_name: "Related Products",
item_variant: "green",
location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo",
price: 10.01,
quantity: 3
},
{
item_id: "SKU_12346",
item_name: "Google Grey Women's Tee",
affiliation: "Google Merchandise Store",
coupon: "SUMMER_FUN",
discount: 3.33,
index: 1,
item_brand: "Google",
item_category: "Apparel",
price: 21.01,
promotion_id: "P_12345",
promotion_name: "Summer Sale",
quantity: 2
}
]
});
```
### 12. Refund
Measure refunds with the relevant transaction ID:
```javascript
gtag("event", "refund", {
currency: "USD",
transaction_id: "T_12345", // Required
value: 30.03,
coupon: "SUMMER_FUN",
shipping: 3.33,
tax: 1.11,
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
price: 10.01,
quantity: 3
// ... other item parameters
}
]
});
```
## Promotions
### View Promotion
Measure promotion impressions:
```javascript
gtag("event", "view_promotion", {
creative_name: "Summer Banner",
creative_slot: "featured_app_1",
promotion_id: "P_12345",
promotion_name: "Summer Sale",
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
// ... other item parameters
}
]
});
```
### Select Promotion
Indicate a user clicked on a promotion:
```javascript
gtag("event", "select_promotion", {
creative_name: "Summer Banner",
creative_slot: "featured_app_1",
promotion_id: "P_12345",
promotion_name: "Summer Sale",
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
// ... other item parameters
}
]
});
```
## Best Practices
### 1. Currency Formatting
- Always set `currency` when sending `value` (revenue) data
- Ensures revenue metrics are calculated correctly
- Use standard 3-letter ISO 4217 currency codes (e.g., "USD", "EUR", "GBP")
### 2. Product IDs
- Use consistent product IDs across all events
- Ensure `item_id` matches your product catalog
- Include both `item_id` and `item_name` for better reporting
### 3. Item Array Structure
- Populate all available ecommerce parameters you have data for
- Even if a parameter is optional, include it when you have the data
- Helps maximize the utility of your ecommerce reporting
### 4. Event Timing
- Fire events at the appropriate time in the user journey
- Don't fire `purchase` events until transaction is confirmed
- Fire `view_item_list` when the list initially loads, not on interaction
### 5. Data Quality Validation
- Use [debug mode](https://support.google.com/analytics/answer/7201382) to verify events in realtime
- Check that parameters match expected values
- Verify currency and value calculations are correct
- Test the complete funnel from browsing to purchase
### 6. Custom Dimensions and Metrics
- Review [custom dimension and metric limits](https://support.google.com/analytics/answer/10075209#limits)
- Plan your custom parameters within these limits
- Document your custom parameter usage
### 7. Sample E-commerce Website
- Use [the Google sample ecommerce website](https://enhancedecommerce.appspot.com/) as a reference
- See practical examples of proper tagging
- Test your implementation against working examples
## Implementation Tips
### Setting Currency at Event Level
When setting the items array outside of the ecommerce object, set the `currency` parameter at the event level:
```javascript
gtag("event", "purchase", {
transaction_id: "T_12345",
currency: "USD", // Event-level currency
value: 72.05,
items: [
// items array
]
});
```
### Consistent Naming Conventions
Maintain consistent naming across your implementation:
- Use snake_case for event names (following GA4 conventions)
- Keep parameter names consistent with GA4 recommended events
- Document any custom parameters you add
## Common Patterns
### Product Listing Pages
```javascript
// On page load
gtag("event", "view_item_list", {
item_list_id: "category_shirts",
item_list_name: "Shirts",
items: [/* all visible products */]
});
// On product click
gtag("event", "select_item", {
item_list_id: "category_shirts",
item_list_name: "Shirts",
items: [/* clicked product */]
});
```
### Product Detail Pages
```javascript
// On page load
gtag("event", "view_item", {
currency: "USD",
value: 29.99,
items: [/* product details */]
});
// On add to cart button click
gtag("event", "add_to_cart", {
currency: "USD",
value: 29.99,
items: [/* product with quantity */]
});
```
### Shopping Cart
```javascript
// When viewing cart
gtag("event", "view_cart", {
currency: "USD",
value: 150.00,
items: [/* all cart items */]
});
// When removing item
gtag("event", "remove_from_cart", {
currency: "USD",
value: 29.99,
items: [/* removed item */]
});
```
### Checkout Flow
```javascript
// Step 1: Begin checkout
gtag("event", "begin_checkout", {
currency: "USD",
value: 150.00,
items: [/* all cart items */]
});
// Step 2: Add shipping
gtag("event", "add_shipping_info", {
currency: "USD",
value: 150.00,
shipping_tier: "Ground",
items: [/* all cart items */]
});
// Step 3: Add payment
gtag("event", "add_payment_info", {
currency: "USD",
value: 150.00,
payment_type: "Credit Card",
items: [/* all cart items */]
});
// Step 4: Complete purchase
gtag("event", "purchase", {
transaction_id: "T_12345",
value: 155.99,
tax: 10.00,
shipping: 5.99,
currency: "USD",
items: [/* all purchased items */]
});
```
## Resources
- [GA4 E-commerce Documentation](https://developers.google.com/analytics/devguides/collection/ga4/ecommerce)
- [GA4 Recommended Events Reference](https://developers.google.com/analytics/devguides/collection/ga4/reference/events)
- [Set up a Purchase Event Guide](https://developers.google.com/analytics/devguides/collection/ga4/set-up-ecommerce)
- [Sample E-commerce Website](https://enhancedecommerce.appspot.com/)

View File

@@ -0,0 +1,472 @@
# Data Layer for Single-Page Applications (SPAs)
**Source**: https://developers.google.com/analytics/devguides/collection/ga4/single-page-applications
**Last Updated**: 2025-01-09
## Overview
Single-Page Applications (SPAs) are websites that load an HTML document once and fetch additional content using JavaScript APIs. Unlike traditional multi-page applications, SPAs require special handling for tracking page views and user interactions because the browser doesn't reload the page when users navigate between different screens.
The key challenge with SPAs is that traditional page view tracking relies on full page loads, but in SPAs, navigation happens dynamically without triggering a new page load event.
## Why SPAs Require Special Treatment
### Traditional vs SPA Page Loads
**Traditional Website:**
- User clicks a link
- Browser requests a new HTML document
- Page fully reloads
- Analytics tags fire automatically on load
**Single-Page Application:**
- User clicks a link
- JavaScript updates the DOM
- URL may change (via History API)
- No page reload occurs
- Analytics tags don't automatically fire
### Key Measurement Goals
To accurately track SPAs, you need to:
1. **Count page views for each screen** a user interacts with
2. **Track the page referrer correctly** to trace the user journey
3. **Maintain proper event sequencing** as users navigate
4. **Clear previous page data** to avoid data carryover
5. **Update page-specific parameters** for each virtual page view
## Implementation Methods
### Method 1: Browser History Changes (Recommended)
Use this method if your SPA uses the [History API](https://developer.mozilla.org/en-US/docs/Web/API/History), specifically the `pushState()` and `replaceState()` methods to update screens.
#### How It Works
GTM's **History Change trigger** listens for:
- Changes to the URL fragment (hash)
- Calls to `history.pushState()`
- Calls to `history.replaceState()`
- Browser back/forward button clicks (`popstate` event)
#### GTM Setup
1. **Enable Built-in History Variables** in GTM:
- History Old URL Fragment
- History New URL Fragment
- History Old State
- History New State
- History Source (pushState, replaceState, popstate, or hashchange)
2. **Create a History Change Trigger:**
- Go to Triggers > New
- Choose "History Change" as trigger type
- Configure any additional filters if needed
- This trigger fires whenever the URL changes without a page reload
3. **Create a Virtual Page View Tag:**
- Create a GA4 Event tag
- Event Name: `page_view`
- Set to fire on the History Change trigger
- Configure page parameters (page_location, page_title, etc.)
#### Example Implementation
```javascript
// Your SPA framework (e.g., React Router) handles navigation
// It uses pushState internally:
history.pushState({ page: 'products' }, 'Products', '/products');
// GTM's History Change trigger automatically detects this
// and fires your configured tags
```
#### Data Layer Push Pattern
When using History Change triggers, push updated page data to the data layer:
```javascript
// Clear previous page data and push new page data
window.dataLayer.push({
event: 'virtual_pageview',
page_path: '/new-page',
page_title: 'New Page Title',
page_location: window.location.href,
// Clear previous page-scoped variables
previous_page_data: undefined,
transaction_id: undefined // Clear transaction-specific data
});
```
### Method 2: Custom Events
Use this method if your website uses the [`DocumentFragment`](https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment) object to render different screens, or if you need more control over when virtual page views fire.
#### How It Works
You manually push custom events to the data layer when screen changes occur, rather than relying on automatic History API detection.
#### Implementation
```javascript
// In your SPA's route change handler
function onRouteChange(newRoute) {
// Update the DOM with new content
updateContent(newRoute);
// Push virtual pageview to data layer
window.dataLayer.push({
event: 'virtual_pageview',
page_path: newRoute.path,
page_title: newRoute.title,
page_location: window.location.href
});
}
```
#### GTM Setup
1. **Create a Custom Event Trigger:**
- Go to Triggers > New
- Choose "Custom Event" as trigger type
- Event name: `virtual_pageview`
2. **Create Variables for Page Data:**
- Data Layer Variable for `page_path`
- Data Layer Variable for `page_title`
- Data Layer Variable for `page_location`
3. **Create a GA4 Event Tag:**
- Event Name: `page_view`
- Add event parameters using the variables created above
- Set to fire on the Custom Event trigger
## Data Layer Clearing Best Practices
### Why Clear Data
In SPAs, data persists in the data layer until explicitly cleared. This can cause:
- Transaction data appearing on non-transaction pages
- User data from one session bleeding into another
- Incorrect attribution of events to pages
### What to Clear
**Page-Scoped Data** (clear on every virtual pageview):
- `page_title`
- `page_path`
- `page_category`
- Custom page dimensions
**Event-Scoped Data** (clear after the event fires):
- `transaction_id`
- `ecommerce` objects
- Form submission data
- Click data
**Persistent Data** (keep across page views):
- `user_id`
- `user_type`
- Session-level dimensions
### Clearing Pattern
```javascript
// Pattern 1: Clear and set in separate pushes
dataLayer.push(function() {
this.reset(); // Clear all data layer state
});
dataLayer.push({
event: 'virtual_pageview',
page_title: 'New Page',
page_path: '/new-page'
});
// Pattern 2: Clear and set in one push (set to undefined)
dataLayer.push({
event: 'virtual_pageview',
// Clear previous values
transaction_id: undefined,
ecommerce: undefined,
form_id: undefined,
// Set new values
page_title: 'New Page',
page_path: '/new-page'
});
```
## Framework-Specific Implementation
### React with React Router
```javascript
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function Analytics() {
const location = useLocation();
useEffect(() => {
// Clear previous page data
window.dataLayer.push({
event: 'virtual_pageview',
page_path: location.pathname,
page_title: document.title,
page_location: window.location.href,
// Clear event-scoped data
transaction_id: undefined,
ecommerce: undefined
});
}, [location]);
return null;
}
export default Analytics;
```
### Vue.js with Vue Router
```javascript
// In your main.js or router configuration
import router from './router';
router.afterEach((to, from) => {
window.dataLayer.push({
event: 'virtual_pageview',
page_path: to.path,
page_title: to.meta.title || document.title,
page_location: window.location.href,
// Clear event-scoped data
transaction_id: undefined,
ecommerce: undefined
});
});
```
### Angular with Angular Router
```typescript
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
export class AppComponent {
constructor(private router: Router) {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe((event: NavigationEnd) => {
window.dataLayer.push({
event: 'virtual_pageview',
page_path: event.urlAfterRedirects,
page_title: document.title,
page_location: window.location.href,
// Clear event-scoped data
transaction_id: undefined,
ecommerce: undefined
});
});
}
}
```
### Next.js
```javascript
// In pages/_app.js
import { useEffect } from 'react';
import { useRouter } from 'next/router';
function MyApp({ Component, pageProps }) {
const router = useRouter();
useEffect(() => {
const handleRouteChange = (url) => {
window.dataLayer.push({
event: 'virtual_pageview',
page_path: url,
page_title: document.title,
page_location: window.location.href,
// Clear event-scoped data
transaction_id: undefined,
ecommerce: undefined
});
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
return <Component {...pageProps} />;
}
export default MyApp;
```
## Testing and Debugging SPAs
### Preview Mode in GTM
1. **Enable Preview Mode** in your GTM container
2. Navigate through your SPA
3. Watch for:
- History Change events firing
- Custom Event triggers activating
- Data layer state changes
- Tag firing sequence
### Debug Console Checklist
For each virtual page view, verify:
- [ ] `page_view` event fires
- [ ] `page_location` updates correctly
- [ ] `page_title` reflects the new screen
- [ ] Previous page's `page_location` becomes current `page_referrer`
- [ ] Event-scoped data is cleared
- [ ] User/session data persists
### DebugView in GA4
1. **Enable Debug Mode** in GTM tags:
```javascript
// Add to your data layer push
window.dataLayer.push({
event: 'virtual_pageview',
debug_mode: true,
// ... other parameters
});
```
2. **Navigate through your SPA**
3. **Check in GA4 DebugView:**
- Each screen change should create a new `page_view` event
- Event parameters should update correctly
- Event sequence should match user journey
### Common SPA Pitfalls
#### 1. Data Carryover
**Problem:** Transaction data appears on non-checkout pages
**Solution:**
```javascript
// Always clear ecommerce data after it's sent
dataLayer.push({
event: 'purchase',
ecommerce: { /* purchase data */ }
});
// On next page view, clear it
dataLayer.push({
event: 'virtual_pageview',
ecommerce: undefined, // Explicitly clear
transaction_id: undefined
});
```
#### 2. Missing Virtual Pageviews
**Problem:** Some navigation doesn't trigger page views
**Solution:** Ensure all route changes push to data layer, including:
- Back/forward button navigation
- Hash changes
- Modal/dialog state changes (if tracking as pages)
#### 3. Duplicate Page Views
**Problem:** Both real and virtual page views fire
**Solution:**
```javascript
// Block the initial page_view from GA4 Config tag
// Use a Pageview trigger with "Fire on DOM Ready" exception
// Or configure GA4 tag to not send page_view on initial load
```
#### 4. Incorrect Referrer
**Problem:** `page_referrer` doesn't reflect previous virtual page
**Solution:** GTM automatically handles this when using History Change triggers. For custom events, ensure you're not manually setting `page_referrer`.
## Advanced Patterns
### Conditional Virtual Pageviews
Only track certain route changes:
```javascript
function shouldTrackPageview(newRoute) {
// Don't track hash-only changes
if (newRoute.startsWith('#')) return false;
// Don't track query parameter-only changes
if (isSamePath(currentRoute, newRoute)) return false;
return true;
}
if (shouldTrackPageview(newRoute)) {
dataLayer.push({
event: 'virtual_pageview',
page_path: newRoute
});
}
```
### Enhanced Virtual Pageview Data
Include additional context with virtual pageviews:
```javascript
dataLayer.push({
event: 'virtual_pageview',
page_path: '/products/category/shoes',
page_title: 'Shoes - Products',
page_location: window.location.href,
// Additional context
page_category: 'Products',
page_subcategory: 'Shoes',
content_group: 'Product Listing',
user_journey_step: 'Browse',
spa_route_type: 'category_page'
});
```
## Verification Checklist
Before deploying your SPA tracking:
- [ ] History Change trigger configured (if using History API)
- [ ] Custom Event trigger configured (if using custom events)
- [ ] Data Layer Variables created for page parameters
- [ ] GA4 Event tag configured with `page_view` event
- [ ] Initial page load tracked correctly
- [ ] All navigation types trigger virtual pageviews:
- [ ] Forward navigation
- [ ] Back button
- [ ] Direct URL changes
- [ ] Hash changes (if applicable)
- [ ] Data clearing implemented for:
- [ ] Transaction data
- [ ] Event-scoped variables
- [ ] Page-scoped variables
- [ ] Tested in Preview Mode
- [ ] Verified in GA4 DebugView
- [ ] Event sequence matches user journey
- [ ] Page referrer updates correctly
## Resources
- [Google Analytics: Measure Single-Page Applications](https://developers.google.com/analytics/devguides/collection/ga4/single-page-applications)
- [GTM History Change Trigger](https://support.google.com/tagmanager/answer/7679322)
- [GTM Built-in Variables for Web](https://support.google.com/tagmanager/answer/7182738)
- [History API Documentation (MDN)](https://developer.mozilla.org/en-US/docs/Web/API/History)
- [Single-Page Applications (MDN)](https://developer.mozilla.org/en-US/docs/Glossary/SPA)