Why this happens
SPA frameworks don't trigger a browser-level page load on route change, so Tealium - built around discrete page loads by default - never sees the new "page" unless you tell it. Two separate problems get conflated under "SPA tracking is broken":
- Nothing fires at all, because there's no page-load event to hook a view call to.
- Something fires, but with data captured at component-mount time rather than at the actual moment of the view or click - so values are stale for anything that resolves after the component renders: async-resolved IDs, personalization content, lazy-loaded product data.
Fix it
- Pick one router lifecycle hook - the "route change complete" event, not "route change started" - as the single place
utag.view()is called for navigation. Don't scatter view calls across multiple components. - Before each
utag.view()call, rebuild the data object from current state. Don't hoist an object built during the component's initial render if the underlying data can still change before this line executes. - Guard every call:
if (window.utag && typeof utag.view === "function") { utag.view({...}); }. This matters more in SPAs than static pages, because the route can change before the library finishes loading on the very first navigation. - For content that mounts after the view already fired - a modal, an infinite-scroll batch, a lazy widget - use
utag.link()from that content's own mount/interaction handler, not the router hook. Treat it as an interaction on the existing view, not a new view. - If the same route can re-render without the URL changing (a tab switch inside one route), decide explicitly whether that's a new
utag.view()or autag.link(), and apply that rule consistently - Tealium has no framework-aware way to infer this for you.
How to verify it worked
- Open Tealium's Trace/Live tool, navigate through several client-side routes without a full reload, and confirm exactly one trace event fires per route change - not zero, not two.
- Inspect
utag.data(orutag_dataon older syntax) in the console immediately after a view fires, and compare it field-by-field to what's actually rendered on screen. A mismatch there is the stale-object symptom, not a firing-timing symptom. - Watch the Network tab for the outbound beacon requests fired by the vendor tags configured on that view. Trace shows what Tealium thinks it sent; the Network tab confirms whether the downstream tag actually sent a beacon.
- For the modal/lazy-content case, trigger the interaction and confirm exactly one
utag.link()call appears in trace, correlated to the click - not to the page load that happened to precede it.