Remix Analytics: Setup for Nested Routes and Transitions
Add privacy-first analytics to Remix: root.tsx installation, client-side transition tracking, and event calls that survive progressive enhancement.
Remix's progressive-enhancement ethos creates a specific analytics requirement most guides miss: your forms work without JavaScript, so your measurement must too — at least for the moments that matter. The setup below handles the standard SPA tracking and the no-JS fallback path.
Installation in root.tsx
// app/root.tsx
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
<script
defer
src="https://clycyo.com/tracker.js"
data-tracking-id="YOUR_TRACKING_ID"
/>
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}Client-side transitions (Link navigations, navigate()) flow through the History API and are recorded automatically as pageviews with transition timing. Nested route changes that alter the URL count; loader revalidations that do not change the URL correctly do not.
Events: enhanced and unenhanced paths
With JS available, track from the component as usual:
<Form method="post" onSubmit={() => {
window.webanalytics?.track('contact_submitted');
}}>For the no-JS path — Remix's specialty — record the conversion server-side in the action, posting to the collect API with a visitor ID you stored in the session when the user first identified (or skip attribution for anonymous no-JS forms; counting beats nothing):
// In your action, after a successful mutation
await fetch('https://clycyo.com/api/collect', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
tracking_id: process.env.CLYCYO_TRACKING_ID,
type: 'event',
visitor_id: session.get('clycyo_visitor_id'),
event_name: 'contact_submitted',
}),
});Identity
// Client, after login
window.webanalytics?.identify(user.email);
// Then persist getVisitorId() via a fetcher to your session/user recordStored once, the visitor ID powers both no-JS action events above and billing-webhook revenue later — one join key for everything.
Automatic coverage
- Pageviews with load and transition timing; field Web Vitals.
- JS errors per route — including ErrorBoundary-swallowed ones that pageview data alone hides.
- Clicks, referrers, first-touch UTM persistence for attribution.
One tag, one optional server-side pattern, and your Remix app measures both of its personalities. Full payload reference in the docs.