Home / Blog / Consent for single-page apps: why route changes break your tag blocking

Consent for single-page apps: why route changes break your tag blocking

Published 2026-10-02

Single-page apps break a quiet assumption that most consent tools were built on: that a page load is a fresh start. On a traditional site, every navigation reloads the document, and the consent tool gets another chance to block tags before they fire. On a single-page app, the document loads once. Every route change after that is JavaScript swapping content in place. If your tag blocking only runs at page load, every route after the first is unguarded.

This is not a theoretical edge case. It is the default behavior of the most common SPA routers, and it produces a specific, repeatable failure: a visitor rejects tracking on the landing page, clicks through to a product page, and the analytics tag fires on the route change because nothing re-checked consent.

Why page-load blocking misses route changes

Most consent wrappers work by intercepting tags at load time. They hold scripts in a queue, check the consent state, and release only the allowed ones. On a full page load this works. The document is fresh, the queue is full, and the gate runs before anything fires.

A route change skips all of that. The document never reloads. Framework-level analytics integrations, the kind wired into the router, fire their pageview events directly on navigation. Tag manager history-change triggers can catch these, but only if someone configured them, and only if the consent tool's blocking logic re-runs on the trigger. Out of the box, many setups do neither. The tag fires because from its perspective nothing happened except a function call.

The consent state race

There is a second failure mode that is subtler. Some SPAs initialize their analytics before the consent tool finishes loading. The app boots, the router fires its first pageview, and the consent banner is still rendering. On a traditional site this window is small and the blocking queue absorbs it. On an SPA with aggressive code splitting, the analytics bundle can win the race by a wide margin.

The fix is ordering, and ordering is a build concern, not a banner concern. The consent state must resolve before the analytics module initializes. That means the consent tool's script needs priority in the bundle or an explicit gate in the app's initialization sequence. If your app's entry point fires analytics unconditionally on boot, no banner configuration will save you.

What correct SPA consent looks like

A correct setup has three properties. First, consent state is read from storage, not from the DOM, so route changes can check it synchronously without waiting for the banner. Second, every analytics call, pageview or event, passes through a consent check at the call site, not just at load. The check is cheap: a stored value compared against the tag's category. Third, consent changes propagate. When a visitor updates their choice in the preference center mid-session, the running app must hear about it and stop or start the relevant tags immediately, without a reload.

That third property is the one most implementations skip. The preference center updates storage and fires a callback, and the app ignores the callback. The visitor chose reject, the banner closed, and the analytics kept flowing for the rest of the session. From the visitor's perspective the site lied.

Testing it

Testing SPA consent needs a different script than testing a traditional site. Load the app, reject everything, and then navigate. Every route change should produce zero analytics network calls. Then open the preference center, accept analytics, navigate again, and confirm the calls resume. Then reject again mid-session and confirm they stop without a reload. Three navigations, three consent transitions, no reloads. If your setup passes that sequence, it is genuinely consent-aware. If it only passes on full reloads, your blocking is load-time only and your routes are unguarded.

Automate this. A headless browser driving the router through those transitions takes minutes to write and catches regressions every time someone touches the analytics integration. Consent regressions in SPAs ship exactly like other regressions: silently, inside a routine dependency update.

The takeaway for builders

The mental model to carry into SPA work is that consent is a runtime state, not a load-time gate. Check it at every call site, propagate changes to the running app, and test through route changes rather than reloads. The banner is the visible part. The call-site checks are the part that actually works.

Get a free consent audit of your website

Free consent audit