Single-page apps

Next.js, Nuxt, React Router, Vue Router and SvelteKit change pages without a reload. The tracker notices on its own, so every route gets its own pageview with no router code.

Updated

How route changes are tracked

The tracker wraps history.pushState and history.replaceState and listens for popstate (the back and forward buttons). After each one it compares the full URL with the last one it counted, and sends a $pageview when it changed. Every modern router uses the History API, so this covers them all.

  • Load the script once, in the document head. Adding it from a component that re-renders could load it again.
  • Don’t send your own pageview events on route change: you’d count every page twice.
  • Only the first pageview of a page load carries referrer. Later route changes are part of the same visit.

Scroll depth per route

Scroll depth is measured per page view. When the route changes, the tracker sends $scroll for the page being left, then starts measuring the new one. It also sends it when the tab is hidden or closed.

Query-string updates

Any URL change counts, including one made with replaceState. An app that writes its filters or search terms into the query string records a pageview each time they change, with the new query string in path. If that’s noisier than you want, add data-query="off" to the script tag so paths are sent without query strings, or filter those paths out in your reports.

Hash routers

The #fragment is never sent, because it can hold anything from a heading anchor to an access token. So an app that routes with the hash (/#/settings, /#/billing) records every route under the same path, often just /. Switch the router to history mode (for example createWebHistory() in Vue Router or createBrowserRouter in React Router), or send a custom event for the screens you care about.

Steps without a URL

Dialogs, wizards and tabs that don’t change the URL aren’t pageviews. When a step matters, send a custom event for it:

checkout.ts
// A multi-step dialog that never changes the URL
function goToStep(step: 'plan' | 'details' | 'payment') {
  window.clickclacks?.('event', 'Checkout step viewed', { step })
}

Next steps