Getting analytics right in a single-page app
Why single-page apps break traditional pageview tracking, how to count route changes correctly, and how sonex handles SPA navigation automatically.
All posts
Single-page apps are fast because they never fully reload. That is also why they quietly break analytics. Traditional trackers count a pageview on page load, and a SPA only loads once, so every in-app navigation after that goes uncounted. Here is how to fix it, and why with sonex there is usually nothing to fix.
Why the pageview disappears
In a classic multi-page site, each link is a full navigation: the browser unloads, requests a new document, and the analytics script runs again, recording a pageview. A single-page app instead swaps content with JavaScript and updates the URL through client-side routing. No document loads, so a load-based tracker sees one pageview for an entire session.
The fix: listen to the History API
Client-side routers navigate by calling the browser’s History API, pushState and replaceState, and the browser fires popstate on back and forward. A SPA-aware tracker wraps those so each route change becomes a pageview.
pushState / replaceState -> count a pageview
popstate (back / forward) -> count a pageview
That is the mechanism every correct SPA tracker uses under the hood.
How sonex handles it
sonex hooks the History API out of the box. Add the script once and client-side route changes are counted as pageviews automatically, with the navigation debounced so a single logical route change is not double counted.
<script defer
src="https://api.trysonex.com/sonex.js"
data-website-id="YOUR_WEBSITE_ID"></script>
For React Router, Vue Router, SvelteKit and the rest, which all navigate through the History API, that is the entire setup.
When you need manual control
A few routing patterns do not use the History API, or you may want a pageview only after data has loaded. For those, send one yourself by calling track with no arguments, which records a pageview for the current URL:
window.sonex.track();
Call it after your route resolves, and the interior view is counted exactly when you intend.
The result
With History-API tracking in place, a single-page app reports like a normal site: real pageviews per route, honest pages-per-visit, and funnels that follow people through your client-side flow, all without a cookie and without slowing the fast navigation that made you build a SPA in the first place.
Frequently asked questions
- Why do single-page apps break analytics?
- Traditional analytics counts a pageview on a full page load. A single-page app changes the URL and content with client-side routing and never reloads, so those in-app navigations go uncounted unless the tracker listens for route changes.
- How does sonex track single-page app navigation?
- sonex hooks the browser History API, pushState, replaceState and the popstate event, so client-side route changes are counted as pageviews automatically. For most SPAs you add the script once and nothing else.
- Do I need to manually send pageviews in React or Vue?
- Usually not. Because sonex listens to History API changes, framework routers that use it are tracked automatically. If you have an unusual routing setup, you can still send a pageview manually through the tracker API.
sonex is privacy-first web analytics. No cookies, no consent banner, no personal data. Drop one script and read realtime visitors, funnels and a world map in seconds.
Try sonex free