Skip to content

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.

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.

The Tracking Tag handles most SPA behavior out of the box:

  • Initial PageView — fires when the app first loads.
  • Dynamically added contentAutoTrack watches the page with a DOM mutation observer, so links and forms your app renders after load are picked up automatically.
  • Click ID substitutionAutoTag 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 FormSubmit event.

The one thing that does not happen automatically: PageView on route changes. Because the page never reloads, you trigger those yourself (see below).

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:

FrameworkWhere 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 Routerpages/_document.tsx
AstroThe base layout, usually src/layouts/Layout.astro
SvelteKitsrc/app.html

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.

src/components/AnyTrackRouteTracker.jsx
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
}
src/components/AnyTrackRouteTracker.jsx
'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.

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.

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.

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 in useEffect, 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. Create src/types/anytrack.d.ts:
// Type declarations for the AnyTrack global JavaScript SDK
declare 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;
}

FAQ & Troubleshooting

FAQ was last reviewed on 2026-07-16

Does AnyTrack work with React, Next.js, and other SPA frameworks?
Yes. The Tracking Tag is plain JavaScript and works with any framework. Install it in the HTML document that hosts your app, then add PageView tracking for route changes as described in this guide.
Do I need to fire PageView manually in a single page app?
Only for route changes. The tag fires the initial PageView automatically when the app loads. Because SPA navigation happens without a page reload, each route change needs a manual AnyTrack PageView trigger, typically wired into your router.
Does AutoTrack detect links and forms my app renders after the page loads?
Yes. AutoTrack watches the page with a DOM mutation observer, so links and forms added dynamically are picked up automatically. See the AutoTrack article for details.
Why does my AnyTrack call fail in a Next.js server component?
AnyTrack only runs in the browser. Server components execute on the server, where the AnyTrack global does not exist. Add the use client directive to the component and wrap the call in useEffect so it runs after the page mounts.
Why do I see duplicate events during development?
Hot module replacement in dev servers like Vite can re-inject the tag while you edit code. This only happens in development mode. In production builds the tag loads once and events fire normally.