Skip to content

SPA Navigation

The tracker automatically detects and tracks single-page application (SPA) navigation. When the route changes without a full page reload, the tracker sends an unload beacon for the previous route and a load beacon for the new one.

How It Works

The tracker intercepts pushState calls and listens to popstate to detect route changes without full page reloads.

History Mode (Default)

The browser adapter wraps history.pushState. When your router calls pushState (from a <NuxtLink>, <RouterLink>, or router.push()), the wrapped version fires all registered onNavigate callbacks.

The adapter also listens to popstate — fired when the user clicks the browser's back or forward buttons.

text
User clicks link → router calls pushState → onNavigate fires

                    ┌─────────────────────────────┘

            sendUnloadBeacon()  (previous page, with duration)


              cleanup()         (new beacon ID, isUnique = false)


            sendLoadBeacon()    (new page, p: false, q: from ping)

Hash Mode

For hash-based routers, the adapter listens to hashchange instead:

ts
const tracker = createTracker({
  apiEndpoint: { track: '...', ping: '...' },
  adapter: createBrowserAdapter({ mode: 'hash' }),
});

When location.hash changes, hashchange fires → onNavigate listeners are called → unload/load cycle runs.

Wrapping Semantics

pushState is wrapped once globally, not per adapter instance:

ts
// First call — wraps history.pushState
const adapter1 = createBrowserAdapter();

// Second call — uses the already-wrapped pushState, adds to listener Set
const adapter2 = createBrowserAdapter();

Both adapters' onNavigate listeners fire when pushState is called. This means multiple tracker instances all detect the same navigation event.

Uniqueness After Navigation

When SPA navigation occurs:

  • isUnique is set to false (the user was already counted as unique today from the initial page load)
  • A new beacon ID is generated
  • A ping is sent for the new page URL (to determine q — whether this page was visited before today)
  • No global ping is sent (the user's uniqueness for the day is preserved)

The subsequent load beacon carries p: false (returning user) and q: true/false (based on whether this specific page path was pinged before today).

Framework Compatibility

Configurations for common routers and frameworks:

FrameworkRouterMode
Vue (Vue Router)createWebHistory()History (default)
Vue (Vue Router)createWebHashHistory()Hash
React (React Router)BrowserRouterHistory (default)
React (React Router)HashRouterHash
Nuxt<NuxtLink> (uses pushState)History (default)
Next.js<Link> (uses pushState)History (default)
SvelteKitClient-side navigationHistory (default)