# How the Plausible Tracker Handles SPA Routing: Hash-Based and pushState Detection

> Plausible automatically tracks SPA routing using hash changes and pushState. Discover how it emits pageview events without full reloads.

- Repository: [Plausible Analytics/analytics](https://github.com/plausible/analytics)
- Tags: internals
- Published: 2026-05-19

---

**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`](https://github.com/plausible/analytics/blob/main/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`](https://github.com/plausible/analytics/blob/main/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:

1. **Initialization** — When `plausible.init()` executes (or automatically for the default web script), it calls `initAutocapture(track)` from [`tracker/src/plausible.js`](https://github.com/plausible/analytics/blob/main/tracker/src/plausible.js) to configure the appropriate listeners based on your routing mode.
2. **Page View Logic** — The local `page()` helper checks `COMPILE_HASH && config.hashBasedRouting` to filter duplicate hash-only navigations. Valid SPA transitions invoke `track('pageview')`, which sends the event to Plausible's API via [`tracker/src/track.js`](https://github.com/plausible/analytics/blob/main/tracker/src/track.js).
3. **Visibility Handling** — Beyond navigation events, Plausible listens for `visibilitychange` and `pageshow` events (checking `event.persisted` for 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:

```javascript
// 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:

```javascript
// 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:

```javascript
// Force a pageview event outside the automatic detection
plausible('pageview');

```

## Summary

- **Dual Detection**: Plausible supports both hash-based routing (via `hashchange` listeners) and History API navigation (via `pushState` patching) in [`tracker/src/autocapture.js`](https://github.com/plausible/analytics/blob/main/tracker/src/autocapture.js).
- **Automatic Instrumentation**: The `initAutocapture(track)` function in [`tracker/src/plausible.js`](https://github.com/plausible/analytics/blob/main/tracker/src/plausible.js) configures the appropriate listeners based on compile flags and runtime configuration.
- **Deduplication**: The `page()` helper filters duplicate hash navigations when `hashBasedRouting` is active.
- **Visibility Resilience**: Additional listeners for `visibilitychange` and `pageshow` ensure 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`](https://github.com/plausible/analytics/blob/main/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.