supapulsedocs
Core packageSPA support

SPA support

How @supapulse/core tracks route changes in single-page apps through the History API, and how to take manual control.

Automatic history tracking

With autoPageviews: true (the default), init():

  1. sends a pageview for the current page;
  2. wraps history.pushState and history.replaceState, sending a pageview after each call;
  3. listens to popstate, sending a pageview on back and forward.

Each pageview is sent on the next tick (setTimeout(…, 0)), so location.href and document.title reflect the new route by then, as long as your router updates the title synchronously.

React Router, Next.js, Remix, Vue Router, SvelteKit and Angular's router all navigate with pushState, so they work without extra code.

Things to know

  • replaceState counts as a pageview. Some routers call it to rewrite the URL on load or to sync query parameters. That shows up as an extra pageview.
  • Hash-only navigation isn't tracked. Changing location.hash fires hashchange, not popstate or pushState. Track it yourself (see below).
  • Titles set after a delay are missed. If your app sets document.title after data loads, pass the title yourself with manual tracking.

Manual control

Turn off automatic pageviews and send them from your router:

supapulse.init({ apiToken: 'pub_your_token', autoPageviews: false });

router.afterEach((to) => {
  supapulse.pageview({ url: location.origin + to.fullPath });
});

Hash routing: Supapulse stores the URL's path, which is / for every #/route URL. Send the route as the path instead:

supapulse.init({ apiToken: 'pub_your_token', autoPageviews: false });

const trackRoute = () =>
  supapulse.pageview({ url: location.origin + (location.hash.slice(1) || '/') });

trackRoute();
addEventListener('hashchange', trackRoute);

Don't mix the two: calling pageview() with autoPageviews: true counts each route twice.

With @supapulse/react

<SupapulseProvider> uses the core tracker, so route changes are tracked the same way. Use the usePageview hook only when you've set autoPageviews: false.

Debugging

Set debug: true and navigate. Each route change should log one [Supapulse] Event sent line with type: 'pageview'. Two lines per navigation means the router calls both pushState and replaceState, or you're also calling pageview() yourself.