Frameworks · 2 min setup
How to add analytics to a React app
<head> of index.html — not into a component. It loads once, and React Router (or any router using the History API) has every route change counted as a pageview without extra code.Updated
A client-rendered React app has one HTML document: index.html at the project root in Vite, or public/index.html in a Create React App project. That's the shell every route renders into, so it's the right place for the tag. Putting it in a useEffect would re-add it on remounts and is harder to keep to exactly one copy.
Using Next.js or React Router's framework mode (formerly Remix)? Use the Next.js guide or the Remix guide instead — those render the <head> from a root component.
React at a glance
- Where the tag goes
index.html(Vite) orpublic/index.html(Create React App)- Route changes
- Tracked automatically with
BrowserRouter/createBrowserRouter - Hash routing
HashRouterchanges aren't separate pageviews (see below)- Custom events
window.visitrack("name", props)- Size
- ~5 KB gzipped, loaded with
defer
How to install VisitTrack on React
Step 1: Copy your site id
Add your site in VisitTrack and copy the snippet from the install step (also in Settings → General). Replace
SITE_IDbelow with your id.Step 2: Paste the tag into index.html
Add it inside
<head>.deferkeeps it from blocking the first render; the script runs after the HTML is parsed and beforeDOMContentLoaded.index.html<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>My app</title> <script defer data-site="SITE_ID" src="https://visitrack.app/tracker.js"></script> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body> </html>Step 3: Optional: a different site id per environment
Localhost is already ignored. To keep staging out of your numbers, the simplest option is Settings → General → Allowed hostnames: once you save your production hosts, hits from any other hostname are dropped. If you'd rather give staging its own VisitTrack site, Vite replaces
%VITE_*%placeholders inindex.htmlat build time — setVITE_VISITRACK_SITE_IDfor every build, because an unset variable is left in the HTML as literal text.index.html (Vite env placeholder)<script defer data-site="%VITE_VISITRACK_SITE_ID%" src="https://visitrack.app/tracker.js"></script>Step 4: Build, deploy and browse
Run your production build, deploy it, and click through a few routes on the live URL.
How to check VisitTrack is working on React
- Open the deployed app and navigate between two routes with your in-app links (no full reload).
- The live view in your VisitTrack dashboard shows you within a few seconds; the Pages tab lists both routes.
- DevTools → Network: each route change sends a
POSTtovisitrack.app/api/collect. - Nothing arriving? Add
data-debugto the tag and read the[visitrack]lines in the console.
Does VisitTrack track React page navigation?
React Router's BrowserRouter and data routers (createBrowserRouter) change the URL with history.pushState. The tracker wraps pushState and replaceState and listens for popstate, so <Link>, useNavigate() and the back button all produce pageviews. Pageviews are keyed on location.pathname: query-string-only changes (filters, ?page=2) aren't counted again.
HashRouter keeps the route after # (/#/settings), and the pathname never changes — so only the first load is recorded. If you need per-route numbers, switch to BrowserRouter (your host must serve index.html for unknown paths) or send a custom event per screen.
How to track custom events and goals in React
Call window.visitrack(name, props) right after the thing actually happened — after your API answers ok, not on click. Props are optional: up to 10 keys, lowercase a-z0-9_-, values up to 255 characters.
For simple clicks, data-vt-goal="pricing_cta" on any element records an event with no handler. data-vt-scroll="pricing_viewed" fires once when that element is at least half visible.
// global.d.ts
declare global {
interface Window {
visitrack?: ((name: string, props?: Record<string, unknown>) => void) & {
identify?: (userId: string, traits?: Record<string, unknown>) => void;
visitorId?: () => string;
};
}
}
export {};
// src/hooks/useTrack.ts
export function track(name: string, props?: Record<string, unknown>) {
window.visitrack?.(name, props);
}
// in a component
<button onClick={async () => {
const ok = await saveProject();
if (ok) track("project_created", { template: "blank" });
}}>Create project</button>After login, call window.visitrack?.identify?.(user.id, { plan: user.plan }) to tie the visitor to your user id; they then appear in People. See People & identify.
Script options you might need on React
All optional — add them to the same tag. Full reference: script configuration.
| Attribute | What it does |
|---|---|
data-cookieless | Store nothing in the browser; the server derives a daily-rotating id. See cookieless mode. |
data-exclude="/app/*" | Never track these paths (comma-separated, * wildcard). Checked on every navigation. |
data-allow-local | Track on localhost and *.local — for testing an install only. |
data-debug | Log every event the script sends, and why it skipped one, to the console. |
data-auto="false" | Turn off automatic events (downloads, contact clicks, form submits, rage clicks, JS errors, 404s). |
React troubleshooting
Only the first page shows up
You're probably on HashRouter; hash changes don't change the pathname. Use BrowserRouter, or track screens with custom events.
Every pageview is counted twice
The tag is in index.html and also injected from a component or a second template. Keep the single copy in index.html.
Events from useEffect fire twice in development
That's React Strict Mode double-invoking effects in dev only. Production builds run effects once — and localhost isn't tracked anyway unless you add data-allow-local.
TypeError: window.visitrack is not a function
A component called it before the deferred script loaded, or a blocker stopped it. Use optional chaining (window.visitrack?.()), or add the queue stub from Script configuration.
React analytics FAQ
Is there a React package or hook for VisitTrack?
No, and you don't need one. The script tag in index.html handles pageviews and route changes; custom events are a single global function, window.visitrack(). A three-line track() helper like the one above is all most apps add.
Does VisitTrack work with React Router v6 and v7?
Yes. Both use the History API in browser routing mode, which the tracker listens to. Declarative <BrowserRouter>, createBrowserRouter and v7's framework mode all work; only hash routing needs custom events per screen.
Where do I put the script in Create React App?
In public/index.html, inside <head>. Create React App is no longer maintained, but the same tag works unchanged after you migrate to Vite (index.html at the project root).
Can I track signups in a React app?
Yes. Call window.visitrack("signup") after your signup request succeeds; an event named signup fills the Signups tab with source and time-to-conversion. See custom events.
Does VisitTrack work with a React app behind a login?
Yes, but you may not want to count it with your marketing site. Add data-exclude="/app/*" to skip those paths, or give the app its own VisitTrack site id.
Is VisitTrack a good Google Analytics alternative for React?
If you want simple, cookie-free numbers with revenue attribution, yes — and it installs with one tag instead of gtag plus a router listener. See VisitTrack vs Google Analytics for an honest comparison.
Keep going
- Next.js analyticsAdd VisitTrack to a Next.js App Router or Pages Router app with next/script — client-side route changes are tracked automatically.
- Remix analyticsAdd VisitTrack to Remix or React Router v7 (framework mode) in app/root.tsx — server-rendered once, with client navigations tracked automatically.
- Vue analyticsAdd VisitTrack to a Vue 3 app built with Vite by adding one tag to index.html — Vue Router history-mode navigations are tracked automatically.
- Angular analyticsAdd VisitTrack to an Angular app in src/index.html — Router navigations are tracked automatically with the default path location strategy.
- Install the tracking scriptThe reference tag, CSP notes and how to check it's working.
- Custom events and signupsvisitrack(), data-vt-goal, scroll events and server-side signups.
- Use casesHow SaaS teams, indie hackers, stores and agencies use VisitTrack.
- VisitTrack vs Google AnalyticsCookie-free analytics with revenue attribution, compared honestly.
Add VisitTrack to your React site
Cookie-free analytics with revenue attribution, live visitors, funnels and session replays. One script tag, every feature on every plan. 14 days free, no card required.
Not on React? See all 29 integration guides.