# SPA support

URL: https://supapulse.app/docs/core/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 [#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 [#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 [#manual-control]

Turn off automatic pageviews and send them from your router:

```ts
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:

```ts
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 [#with-supapulsereact]

`<SupapulseProvider>` uses the core tracker, so route changes are tracked the same way. Use the [`usePageview`](https://supapulse.app/docs/react/hooks#usepageview) hook only when you've set `autoPageviews: false`.

## Debugging [#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.
