Single-page apps and route changes
Client-side navigation is counted automatically, including the back button; hash routers need one attribute.
Single-page apps need no extra code. The script watches the URL itself and records a pageview whenever it changes.
How it works
On load, the script wraps history.pushState and history.replaceState, and listens for popstate. After each of those, it compares the current path and query string with the last one it saw. If they differ, it records a pageview.
Every client-side router in common use navigates that way, so React Router, Vue Router, the Next.js and Nuxt routers, SvelteKit, Remix, Astro view transitions and Turbo are all counted without configuration. The back and forward buttons are counted too.
What happens on a route change
- The scroll depth reached on the page you are leaving is sent, then the counter resets for the new page.
- A pageview is recorded for the new URL.
- Heatmap recording is re-evaluated, because it is configured per path.
- Widgets that are limited to certain pages are shown or hidden.
Hash routers
A hash router keeps the path the same and changes only the fragment after #, so by default those navigations are not pageviews. Opt in on the tag:
<script defer data-website-id="ws_XXXXXXXX" data-domain="yourstartup.com" data-hash-routing="true" src="https://statsy.co/js/script.js"></script>The fragment then becomes part of the URL the script compares, and #/pricing is a page of its own.
Doing it by hand
If you have a router that changes views without touching the History API, record the pageview yourself after the view changes:
statsy.pageview();Watch out. Do not add a route-change listener that calls
statsy.pageview()on top of the automatic tracking, and do not load the script again on navigation. Either one doubles every pageview. This is the most common mistake in Google Tag Manager setups.
Last updated · Markdown version