---
title: GTM Data Layer: Preventing Stale Data During SPA Navigation
slug: experience/gtm-data-layer-preventing-stale-data-during-spa-navigation
docTags: 
createdAt: 2026-06-19T12:38:59.704Z
---

Google Tag Manager's data layer is a cumulative key-value store. Every call to `dataLayer.push()` **deep-merges** the pushed object into GTM's internal model - it never removes existing keys on its own. A key is only updated when a push explicitly includes it.

Example:

```javascript
// Navigation to Article A
dataLayer.push({
  purple: {
    view_event_name: "article_view",
    parameters: {
      article_id:   "article-a",
      publish_date: "2024-01-10",
      author:       "Jane Doe"
    }
  }
})

// Navigation to Article B (author not provided this time)
dataLayer.push({
  purple: {
    view_event_name: "article_view",
    parameters: {
      article_id:   "article-b",
      publish_date: "2024-03-22"
      // author is missing - Jane Doe is still in GTM's model
    }
  }
})
```

After the second push, GTM's internal model for `purple.parameters.author` still contains `"Jane Doe"` from Article A.

**SPA mode vs. non-SPA mode**

In SPA mode (the default Purple storefront behavior), the JavaScript environment persists for the entire session. The `dataLayer` array and GTM's internal model are never reset - every push accumulates on top of the previous state.

In non-SPA mode (where each navigation causes a full-page reload), the JavaScript environment is destroyed and recreated on each page. `dataLayer` is re-initialized as an empty array, so stale data from previous pages is impossible. If your storefront is configured in non-SPA mode, this guide does not apply.

## The Purple behavior

When the Purple storefront fires a view event via the GTM tracking plugin, it pushes to `window.dataLayer` under the `purple` namespace. The parameters included in that push are&#x20;**&#x20;the parameters listed in&#x20;**`tracking_config.json` for that particular view/event.

For example, if view A defines `author` as a parameter but view B does not, navigating from A to B leaves the `author` value from view A in GTM's model. Any GTM tag that reads `purple.parameters.author` will see stale data.

This is most visible with article/content views where fields like publish date, author, taxonomy, or image URLs are defined inconsistently across different view configurations.

## Use explicit Parameters in tracking\_config.json

List **every parameter you want to track in the&#x20;**`parameters`**&#x20;section of every affected view**, even if the value will not always be available.

When a placeholder like `{{AUTHOR}}` cannot be resolved (because the current page has no author data), the storefront pushes an **empty string** `""` for that key. This overwrites the stale value from the previous view with an empty string, which is the correct behavior - GTM reads no author for the current view.

In `tracking_config.json`, add the full set of content-related parameters to every view that could follow a content view during SPA navigation:

```json
{
  "google_tag_manager": {
    "viewsEnabledByDefault": true,
    "views": {
      "STOREFRONT_HOME": {
        "templates": {
          "name": "storefront_home"
        },
        "parameters": {
          "view":             "{{VIEW}}",
          "content_id":       "{{CONTENT_ID}}",
          "content_name":     "{{CONTENT_NAME}}",
          "issue_id":         "{{ISSUE_ID}}",
          "issue_name":       "{{ISSUE_NAME}}",
          "publication_id":   "{{PUBLICATION_ID}}",
          "publication_name": "{{PUBLICATION_NAME}}"
        }
      },
      "ISSUE_CONTENT": {
        "templates": {
          "name": "issue_content"
        },
        "parameters": {
          "view":             "{{VIEW}}",
          "content_id":       "{{CONTENT_ID}}",
          "content_name":     "{{CONTENT_NAME}}",
          "issue_id":         "{{ISSUE_ID}}",
          "issue_name":       "{{ISSUE_NAME}}",
          "publication_id":   "{{PUBLICATION_ID}}",
          "publication_name": "{{PUBLICATION_NAME}}"
        }
      }
    }
  }
}
```

The key rule: **if a parameter appears in any view, it should appear in all views that can be reached from it via SPA navigation without a full page reload.**

For project-specific fields (stored as custom issue properties), the placeholder follows the pattern `{{ISSUE_PROPERTY_<KEY>}}`. For example, a custom property `publish-date` would be `{{ISSUE_PROPERTY_PUBLISH_DATE}}`.

## Available Standard Placeholders

These placeholders are resolved by the storefront at the time the view/event fires:

**Content / Article**

- `{{CONTENT_ID}}` - ID of the current content item
- `{{CONTENT_NAME}}` - Name/title of the current content item

**Issue**

- `{{ISSUE_ID}}`, `{{ISSUE_NAME}}`, `{{ISSUE_PRODUCT_ID}}`
- `{{ISSUE_PRICE}}`, `{{ISSUE_PRICE_VALUE}}`, `{{ISSUE_PRICE_CURRENCY}}`
- `{{ISSUE_PURCHASABLE}}`, `{{ISSUE_PURCHASED}}`, `{{ISSUE_LATEST}}`
- `{{ISSUE_PROPERTY_<KEY>}}` - Any custom issue property

**Publication**

- `{{PUBLICATION_ID}}`, `{{PUBLICATION_NAME}}`, `{{PUBLICATION_TYPE}}`
- `{{PUBLICATION_PROPERTY_<KEY>}}` - Any custom publication property

**Navigation**

- `{{VIEW}}` - The current storefront view name
- `{{POPUP}}` - Whether the view is open as a popup

**Page (readmode)**

- `{{PAGE_ID}}`, `{{PAGE_INDEX}}`, `{{PAGE_NUMBER}}`, `{{PAGE_LABEL}}`, `{{PAGE_TITLE}}`, `{{PAGE_ALIAS}}`, `{{PAGE_SECTION}}`

**Subscription / Purchase**

- `{{SUBSCRIPTION_ID}}`, `{{SUBSCRIPTION_NAME}}`, `{{SUBSCRIPTION_TYPE}}`
- `{{PRODUCT_ID}}`, `{{PRODUCT_NAME}}`, `{{PRODUCT_PRICE}}`
- `{{TRANSACTION_ID}}`, `{{CURRENCY_CODE}}`

## Verification

**GTM Preview Mode (Tag Assistant)**

1. Open the storefront in Chrome with GTM Preview Mode active
2. Navigate to a content view (Article A)
3. Inspect the data layer in Tag Assistant - note all `purple.parameters` values
4. Navigate to a different content view (Article B) without reloading
5. Check `purple.parameters` again - all fields should reflect Article B only, with empty strings for any fields not applicable to Article B

**Experience Logs**

The Purple storefront has a built-in tracking debugger. Enable it in the browser console while in preview mode:

```js
window.purple.tracking.debug({ logging: true })
```

Or persistently via localStorage (survives page refresh):

```js
localStorage.setItem('tracking.debug.logging', 'true')
// then reload the page
```

Once enabled, every tracked event is printed to the console:

```javascript
[TRACK] [VIEW] ISSUE_CONTENT
Parameters:
  content_id    = article-b,
  content_name  = My Article B,
  issue_id      = issue-456,
  publish_date  = ,
  author        = {{AUTHOR}}
```

Fields that resolve to their placeholder (because no value is available for the current view) show as empty. This confirms they are being explicitly pushed and will overwrite stale values in GTM's model.

Navigate between two articles and verify that all listed parameters reflect the current article on each view event and that fields not applicable to the current article appear as empty strings rather than retaining values from the previous article.
