Frameworks · 2 min setup
How to add analytics to Remix and React Router v7
<head> in the Layout of app/root.tsx. It's server-rendered into every document, and every client-side navigation through <Link> or useNavigate() is counted as a pageview automatically.Updated
Remix v2 and React Router v7's framework mode share the same model: app/root.tsx renders the whole HTML document, including <head>. A plain <script> element there is part of the server HTML, so the browser loads it once; React hydrates around it without running it again.
Remix at a glance
- Where the tag goes
<head>in theLayoutexport ofapp/root.tsx- Route changes
- Tracked automatically
- Server-side events
- From
actions viaPOST /api/track - Works with
- Remix v2, React Router v7 framework mode, SPA mode
- Cookie banner
- Not needed in cookieless mode
How to install VisitTrack on Remix
Step 1: Copy your site id
Copy the snippet from VisitTrack's install step (or Settings → General) and replace
SITE_IDbelow.Step 2: Add the tag to app/root.tsx
In React Router v7 the imports come from
react-router; in Remix v2 they come from@remix-run/react. Everything else is identical.app/root.tsximport { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"; export function Layout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta /> <Links /> <script defer data-site="SITE_ID" src="https://visitrack.app/tracker.js" /> </head> <body> {children} <ScrollRestoration /> <Scripts /> </body> </html> ); } export default function App() { return <Outlet />; }Step 3: Allow it in your CSP, if you send one
If you set a
Content-Security-Policyinentry.server.tsxor at your host, addhttps://visitrack.apptoscript-srcandconnect-src.Step 4: Deploy and browse
The dev server runs on localhost, which is ignored. Deploy and click between routes on the live site.
How to check VisitTrack is working on Remix
- View source on a deployed page — the tag is in the server HTML, inside
<head>. - Navigate between routes with
<Link>s; the VisitTrack live view shows you and the Pages tab lists each route. - DevTools → Network: one
POSTtovisitrack.app/api/collectper navigation. Loaderfetches anduseFetchercalls don't change the URL, so they never add pageviews.
Does VisitTrack track Remix page navigation?
Remix and React Router navigate with history.pushState, which the tracker wraps (along with replaceState and popstate). <Link>, <Form> submissions that redirect, redirect() from an action and useNavigate() all land as pageviews once the URL changes.
Search-param-only updates — setSearchParams({ page: "2" }) — keep the same pathname and aren't counted again, which keeps filters and pagination from inflating pageviews.
How to track custom events and goals in Remix
The most reliable place to record a signup is the server, in the action that created the account. Pass the visitor id from the browser in a hidden field so the event joins the visitor's earlier pageviews and source.
// In the form (browser): <input type="hidden" name="vid" value={visitorId} />
// where visitorId = window.visitrack?.visitorId?.() read in a useEffect.
export async function action({ request }: Route.ActionArgs) {
const form = await request.formData();
const user = await createUser(String(form.get("email")));
const visitorId = String(form.get("vid") || "");
if (visitorId) {
await fetch("https://visitrack.app/api/track", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VISITRACK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "event",
visitorId, // the _ana_vid value the browser sent you
name: "signup",
props: { method: "email" },
}),
});
}
return redirect("/welcome");
}In the browser, window.visitrack?.("name", props) and data-vt-goal="…" work as in any React app. Server events need a secret key from Settings → API / MCP — never ship it to the client. Full reference: server-side events.
Revenue attribution on Remix
Start Stripe Checkout from an action and pass the same visitor id as client_reference_id; VisitTrack's webhook then credits the payment to the visitor's first referrer, campaign and landing page. Lemon Squeezy, Polar, Paddle and Razorpay each have their own metadata field — see Revenue attribution and Stripe.
Script options you might need on Remix
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). |
Remix troubleshooting
Hydration warning mentioning the script
Make sure the tag is rendered identically on server and client — a hard-coded id, not one read from window. Reading the id from an env var in a loader is fine.
Pageviews doubled
The tag is in root.tsx and again in a nested route's head or a <script> from a component. Keep the single copy in Layout.
The server event returns 400
visitorId was empty or the body isn't valid JSON. Check the hidden field is filled before submit (it's set in an effect after the tracker loads).
Nothing in dev
Localhost is ignored by design. Add data-allow-local temporarily to test.
Remix analytics FAQ
Where do I add a script tag in Remix?
In app/root.tsx, inside the head rendered by the Layout export (or the default App component on older Remix versions). That file renders the document for every route, so the tag loads on every page exactly once.
Does VisitTrack track Remix client-side navigation?
Yes. Remix and React Router change the URL with the History API, and the tracker counts each pathname change as a pageview. No useLocation effect is needed.
Does this work with React Router v7 SPA mode (ssr: false)?
Yes. The root Layout is still rendered into the HTML shell at build time, so the tag is present on load, and navigation is tracked in the browser.
How do I track signups in Remix?
Send a signup event from the action that creates the account with POST /api/track, passing the visitor id from a hidden form field — or call window.visitrack("signup") in the browser after the action succeeds. Do one or the other, not both.
Can VisitTrack attribute Stripe revenue in a Remix app?
Yes. Pass the visitor id as client_reference_id when you create the Checkout Session, and connect the Stripe webhook in Settings → Revenue. See Stripe revenue attribution.
Keep going
- React analyticsAdd VisitTrack to a React single-page app (Vite, Create React App) through index.html — React Router navigations are tracked automatically.
- Next.js analyticsAdd VisitTrack to a Next.js App Router or Pages Router app with next/script — client-side route changes are tracked automatically.
- Vercel analyticsUse VisitTrack on Vercel: add the tag in your framework, keep preview deployments out with VERCEL_ENV or allowed hostnames, and set CSP headers.
- Cloudflare Pages analyticsAdd VisitTrack on Cloudflare Pages in your HTML or with an HTMLRewriter middleware, exclude it from Rocket Loader, and track AI crawlers at the edge.
- 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 Remix 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 Remix? See all 29 integration guides.