Single-Page Application Support

Overview

The tracking script includes built-in support for single-page applications (SPAs). It automatically detects URL changes, re-evaluates experiences, and handles DOM mutations — no configuration required for most setups.

Automatic URL Change Detection

The script monitors three navigation events:

  • history.pushState() — Programmatic navigation (React Router, Vue Router, etc.)
  • history.replaceState() — URL replacement without navigation
  • popstate — Browser back/forward buttons

When a URL change is detected, the script:

  1. Fires the url.changed lifecycle event
  2. Re-evaluates location triggers against the new URL
  3. Activates new experiences that match the new page
  4. Cleans up changes from experiences that no longer apply
  5. Refreshes goal listeners

This means experiences are re-evaluated on every SPA page transition automatically.

Disabling SPA Detection

If SPA detection causes issues, you can disable it with a query parameter:

https://example.com?_conv_disable_spa_optimizations=true

Continuous Activation

Beyond URL changes, the script observes the DOM for mutations and continuously applies variation changes when their target selectors appear. This handles:

  • Lazy-loaded content — Variations apply when elements render, even after initial page load
  • Framework hydration — Changes survive React/Vue/Angular hydration cycles
  • Dynamic rendering — Elements added after page load are detected and modified

Visual Editor Changes

Changes made via the Visual Editor are handled automatically. The script tracks which changes have been applied and prevents duplicates — you don't need to do anything special for SPAs.

Custom JavaScript Changes

When using custom JavaScript (in VariationJS or ExperienceJS), you are responsible for preventing duplicates. The script may re-execute your code on SPA transitions. Guard with an element check:

// VariationJS
(() => {
  const id = "convert-custom-banner";
  if (document.querySelector(`#${id}`)) return;
  document.body.insertAdjacentHTML("afterbegin", `<div id="${id}">Banner</div>`);
})();

Tuning Continuous Activation

For SPAs with edge cases, you can fine-tune the script's behaviour using setParameters in Global JavaScript:

Delay Activation

If variation changes to navigation links cause conflicts when clicked:

_conv_q.push({
  what: "setParameters",
  params: { delayContinuousActivation: 100 } // milliseconds
});

Throttle DOM Reactions

If the SPA produces constant DOM mutations (real-time data, animations):

_conv_q.push({
  what: "setParameters",
  params: { throttleChanges: 100 } // milliseconds
});

Both parameters accept millisecond values. Start with 100 and adjust as needed.

Listening for URL Changes

You can react to SPA navigation in your own code:

window._conv_q = window._conv_q || [];
window._conv_q.push({
  what: 'addListener',
  params: {
    event: 'url.changed',
    handler: function(data) {
      console.log('Navigated from', data.from, 'to', data.to);
    }
  }
});

Notes

  • SPA detection works with all modern frameworks (React, Vue, Angular, Svelte, Next.js, Nuxt, etc.)
  • The script re-runs the full evaluation cycle on each URL change, so location-based targeting works correctly across SPA pages
  • Changes are cleaned up when their associated experience is no longer active on the new page
  • The throttleChanges and delayContinuousActivation parameters can be combined