How the Plausible Tracker Handles SPA Routing: Hash-Based and pushState Detection
Plausible Analytics automatically tracks Single Page Application (SPA) navigation by instrumenting browser history methods and monitoring hash changes, emitting pageview events whenever the URL updates without triggering a full page reload.
Single Page Applications present unique challenges for privacy-focused analytics because they dynamically update content without traditional browser refreshes. The Plausible tracker in the plausible/analytics repository solves this through a dual-path detection system that captures both hash-based routing and HTML5 History API navigation as distinct pageviews.
How Plausible Detects SPA Navigation
The tracker distinguishes between routing paradigms using compile-time flags and runtime configuration options defined in tracker/src/config.js. Depending on your application architecture, Plausible activates one of two detection mechanisms during initialization.
Hash-Based Routing Detection
When the COMPILE_HASH compilation flag is true and the runtime option hashBasedRouting is enabled, Plausible attaches a hashchange event listener inside tracker/src/autocapture.js. Every modification to the URL hash fragment (e.g., example.com/#/about changing to example.com/#/contact) triggers the internal page(true) helper function, which subsequently invokes track('pageview') to record the navigation event.
History API Navigation (pushState and popstate)
For applications utilizing the History API, Plausible patches window.history.pushState directly. The original native method is stored in a closure, then wrapped with custom logic that calls onSPANavigation() after the original pushState execution. This wrapper ensures that programmatic navigation (such as React Router's push() calls) immediately triggers page(true) and emits a tracking event. Additionally, Plausible attaches a popstate listener to capture back and forward button navigation through the browser history stack.
Core Tracking Flow
The SPA detection system operates through a coordinated initialization sequence:
- Initialization — When
plausible.init()executes (or automatically for the default web script), it callsinitAutocapture(track)fromtracker/src/plausible.jsto configure the appropriate listeners based on your routing mode. - Page View Logic — The local
page()helper checksCOMPILE_HASH && config.hashBasedRoutingto filter duplicate hash-only navigations. Valid SPA transitions invoketrack('pageview'), which sends the event to Plausible's API viatracker/src/track.js. - Visibility Handling — Beyond navigation events, Plausible listens for
visibilitychangeandpageshowevents (checkingevent.persistedfor bfcache restores) to ensure pageviews fire when users return to previously loaded tabs.
Configuration Examples
Enabling Hash-Based Routing
For applications using Vue Router in "hash" mode or similar hash-based frameworks, explicitly enable hash tracking during initialization:
// Configure before loading the Plausible script
window.plausible = window.plausible || [];
window.plausible.q = window.plausible.q || [];
// Initialize with hash-based routing enabled
plausible('init', { hashBasedRouting: true });
History API Usage (Automatic)
Standard SPAs using React Router, Vue Router in "history" mode, or Next.js require no special configuration:
// Standard initialization
plausible('init');
// Navigation like router.push('/dashboard') automatically triggers
// pushState patching → onSPANavigation() → pageview tracking
Manual Pageview Triggering
For edge cases or custom routing implementations, trigger pageviews programmatically:
// Force a pageview event outside the automatic detection
plausible('pageview');
Summary
- Dual Detection: Plausible supports both hash-based routing (via
hashchangelisteners) and History API navigation (viapushStatepatching) intracker/src/autocapture.js. - Automatic Instrumentation: The
initAutocapture(track)function intracker/src/plausible.jsconfigures the appropriate listeners based on compile flags and runtime configuration. - Deduplication: The
page()helper filters duplicate hash navigations whenhashBasedRoutingis active. - Visibility Resilience: Additional listeners for
visibilitychangeandpageshowensure accurate tracking across browser back-forward cache scenarios.
Frequently Asked Questions
Do I need to configure anything for React Router or Vue Router?
No. Plausible automatically detects History API navigation used by React Router, Vue Router (history mode), and Next.js by patching window.history.pushState in tracker/src/autocapture.js. No manual configuration is required unless you use hash-based routing, in which case you must set hashBasedRouting: true during initialization.
How does Plausible handle the back button in SPAs?
The tracker attaches a popstate event listener that invokes onSPANavigation() whenever users navigate backward or forward through their history. This ensures that page(true) runs and a pageview is recorded even when the browser history state changes rather than the URL hash.
Can I disable automatic SPA tracking?
While there is no explicit "disable SPA" flag, you can prevent automatic hash-based tracking by ensuring hashBasedRouting remains false (the default). For History API applications, the pushState patching occurs automatically in the compiled script, but you can avoid triggering events by not calling the initialization function if you require completely manual control.
Why would I need to manually trigger a pageview?
Manual plausible('pageview') calls are useful for custom routing implementations that bypass standard History API methods, soft navigation within tabbed interfaces, or when tracking specific user actions as distinct page events that don't correspond to URL changes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →