Single Page App Tracking
Set up AnyTrack conversion tracking in single page applications built with React, Next.js, Vite, or AI coding tools like Lovable and Bolt. Covers Tracking Tag installation, PageView tracking on route changes, and form click ID setup.
Overview
Section titled “Overview”A single page application (SPA) loads once and updates content through client-side routing instead of full page reloads. Frameworks like React, Next.js, Vue, and Svelte work this way, and so do apps built with AI coding tools such as Lovable, Bolt, and v0.
This changes how tracking behaves. On a traditional website, the Tracking Tag reloads with every page and fires a new PageView each time. In an SPA, the tag loads once and navigation happens without a reload, so route changes are invisible to AnyTrack until you trigger them.
This guide covers what the Tracking Tag handles automatically in an SPA, where to install it in popular frameworks, and the two additions most SPAs need: PageView tracking on route changes and click ID handling in forms.
What Works Automatically
Section titled “What Works Automatically”The Tracking Tag handles most SPA behavior out of the box:
- Initial
PageView— fires when the app first loads. - Dynamically added content — AutoTrack watches the page with a DOM mutation observer, so links and forms your app renders after load are picked up automatically.
- Click ID substitution — AutoTag replaces the
--CLICK-ID--placeholder in hidden form fields and form action URLs with the real click ID at runtime. - Form submissions — AutoTrack detects standard HTML form submissions and fires a
FormSubmitevent.
The one thing that does not happen automatically: PageView on route changes. Because the page never reloads, you trigger those yourself (see below).
Install the Tracking Tag
Section titled “Install the Tracking Tag”The tag itself is the same as on any website: paste the snippet from your AnyTrack dashboard into the <head> of the HTML document that hosts your app, before other scripts. Follow the standard installation guide to get your snippet. What varies by framework is where that document lives:
| Framework | Where to paste the tag |
|---|---|
| React (Vite, Create React App, Lovable, Bolt) | index.html at the project root, inside <head> |
| Next.js App Router (v13+) | app/layout.tsx, using the Next.js Script component with the beforeInteractive strategy |
| Next.js Pages Router | pages/_document.tsx |
| Astro | The base layout, usually src/layouts/Layout.astro |
| SvelteKit | src/app.html |
Track Route Changes
Section titled “Track Route Changes”AutoTrack fires PageView once, on the initial load. On each route change, trigger it yourself:
AnyTrack('PageView');Hook this into your router so it fires on every navigation.
React Router
Section titled “React Router”import { useEffect, useRef } from 'react';import { useLocation } from 'react-router-dom';
// Drop this component inside your <Router> to track// SPA route changes as AnyTrack PageView events.export function AnyTrackRouteTracker() { const location = useLocation(); const didMount = useRef(false);
useEffect(() => { // Skip the first run: the tag already fires the initial PageView if (!didMount.current) { didMount.current = true; return; } AnyTrack(function() { AnyTrack('PageView'); }); }, [location.pathname]);
return null; // renders nothing}Next.js App Router
Section titled “Next.js App Router”'use client';
import { useEffect, useRef } from 'react';import { usePathname } from 'next/navigation';
export function AnyTrackRouteTracker() { const pathname = usePathname(); const didMount = useRef(false);
useEffect(() => { // Skip the first run: the tag already fires the initial PageView if (!didMount.current) { didMount.current = true; return; } AnyTrack(function() { AnyTrack('PageView'); }); }, [pathname]);
return null;}Add the component to app/layout.tsx inside the <body>.
Both examples skip their first run because the tag already fires the initial PageView on load; without that guard the first page would be counted twice. They react to path changes only — if query string changes represent distinct pages in your app, add the search params to the effect dependencies.
Track Forms in an SPA
Section titled “Track Forms in an SPA”Form tracking in an SPA uses the same hidden field pattern as any other site: add a hidden input with the --CLICK-ID-- placeholder, and AutoTag fills it with the real click ID when the page loads.
<input type="hidden" name="atclid" value="--CLICK-ID--" />When the form submits, read the hidden field value in your submit handler and include it in the payload you send to your backend or CRM. AutoTrack fires the FormSubmit event in the browser automatically.
See Form Tracking for the full hidden field setup. If your form is an embedded third-party widget that cannot hold hidden fields, use the Append Click ID to URL method instead.
Fire Conversion Events from Components
Section titled “Fire Conversion Events from Components”For events that do not come from a form, such as a purchase after payment, trigger them from your component code:
AnyTrack(function() { AnyTrack('trigger', 'Purchase', { value: 49.90, // number, not a string currency: 'USD', transactionId: 'order-12345' // unique ID prevents duplicates });});See Standard Events for the full list of event names and Event Attributes for the data you can attach.
Next.js and SSR Pitfalls
Section titled “Next.js and SSR Pitfalls”Server-side rendering adds a few constraints, because AnyTrack only runs in the browser:
- Server components: Next.js App Router components are server components by default. Add the
'use client'directive to any component that calls AnyTrack. - Call inside
useEffect: Never call AnyTrack during component rendering. It runs on the server there and crashes. Wrap calls inuseEffect, which only runs in the browser after mount. - TypeScript errors: Without a type declaration, the IDE flags
AnyTrack is not defined, and AI coding tools may remove the calls. Createsrc/types/anytrack.d.ts:
// Type declarations for the AnyTrack global JavaScript SDKdeclare function AnyTrack(command: 'PageView'): void;declare function AnyTrack(command: 'trigger', eventName: string, attributes?: Record<string, any>): string;declare function AnyTrack(command: 'atclid'): string;declare function AnyTrack(callback: () => void): void;
interface Window { AnyTrack: typeof AnyTrack;}Related Resources
Section titled “Related Resources”FAQ & Troubleshooting
FAQ was last reviewed on 2026-07-16