Adding Analytics to Remix Apps in 2026
Remix v2 and React Router v7 are now effectively the same thing — React Router v7 is the path forward for Remix apps, with file-based routing, server-side rendering, and the loader/action model that makes Remix feel different from other React frameworks.
That hybrid architecture creates a few wrinkles when adding analytics. You need client-side tracking for SPA navigation, and you want server-side event tracking for actions that happen without a full page load. Measure.events handles both.
Here’s how to add privacy-first analytics (900 bytes, no cookies, no consent banner) to a Remix or React Router v7 app.
Script Injection via root.tsx links Export
In Remix and React Router v7, the right place to inject global scripts is root.tsx. Use the links export to declare the script — Remix will handle placement in the document head:
// app/root.tsx
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
// or: from "react-router" in React Router v7
export function links() {
return [
{
rel: "preconnect",
href: "https://lets.measure.events",
},
];
}
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
<script
defer
data-site-key="YOUR_SITE_KEY"
src="https://lets.measure.events/api/script/YOUR_SITE_KEY"
/>
</head>
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
The preconnect link hint tells the browser to establish a connection to the analytics domain early, reducing latency when the deferred script finally loads.
Client-Side Pageview Tracking with useEffect and useLocation
Remix is an SPA after the first load — navigating between routes doesn’t reload the page. You need to track route changes explicitly using useLocation:
// app/root.tsx (updated)
import { useEffect } from "react";
import { useLocation } from "@remix-run/react";
// or: from "react-router" in React Router v7
declare global {
interface Window {
measure?: {
track: (event: string, data?: Record<string, unknown>) => void;
trackPageview: (data?: { path?: string }) => void;
};
}
}
function Analytics() {
const location = useLocation();
useEffect(() => {
// Fire on every route change
window.measure?.trackPageview({ path: location.pathname + location.search });
}, [location.pathname, location.search]);
return null;
}
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
<script
defer
data-site-key="YOUR_SITE_KEY"
src="https://lets.measure.events/api/script/YOUR_SITE_KEY"
/>
</head>
<body>
<Analytics />
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
The Analytics component renders nothing but fires a pageview event every time the route changes. Putting it inside App (inside the router context) gives it access to useLocation.
Custom Event Tracking in Components
For tracking user interactions, create a small utility that wraps the window.measure API:
// app/utils/analytics.ts
export function track(event: string, data: Record<string, unknown> = {}): void {
window.measure?.track(event, data);
}
Use it in your route components:
// app/routes/pricing.tsx
import { track } from "~/utils/analytics";
export default function Pricing() {
function handleSignupClick(plan: string) {
track("signup_click", { plan, source: "pricing_page" });
}
return (
<div>
<button onClick={() => handleSignupClick("pro")}>
Start Pro Trial
</button>
</div>
);
}
Server-Side Events via Resource Routes
This is where Remix’s server/client model gets interesting. Sometimes you want to track events that happen server-side — form submissions, purchases, API calls — without relying on client-side JavaScript.
Create a resource route that accepts POST requests and forwards events to Measure.events:
// app/routes/api.track.ts
import { ActionFunctionArgs, json } from "@remix-run/node";
// or: from "react-router" in React Router v7
export async function action({ request }: ActionFunctionArgs) {
const { event, data } = await request.json();
await fetch("https://lets.measure.events/api/v1/events", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.MEASURE_API_KEY}`,
},
body: JSON.stringify({ event, data }),
});
return json({ ok: true });
}
Call it from your actions when something important happens:
// app/routes/checkout.tsx
import { ActionFunctionArgs, redirect } from "@remix-run/node";
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const plan = formData.get("plan") as string;
// ... process checkout ...
// Track server-side — happens even if client JS fails
await fetch(`${process.env.BASE_URL}/api/track`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
event: "checkout_complete",
data: { plan },
}),
});
return redirect("/dashboard");
}
Server-side tracking is especially useful for:
- Purchase confirmations (don’t lose the event if the user closes the tab)
- Form submissions processed on the server
- API endpoint usage
- Webhook handlers
SSR Considerations
Remix renders on the server first. The tracking script uses defer so it only loads client-side, but be careful with direct window.measure calls in code that runs server-side.
The utility function approach handles this safely:
// app/utils/analytics.ts
export function track(event: string, data: Record<string, unknown> = {}): void {
// Safe to call anywhere — no-ops on the server
if (typeof window !== "undefined") {
window.measure?.track(event, data);
}
}
MCP Server Setup for AI-Powered Queries
Once your analytics are collecting data, set up the MCP server to query it from your AI tools:
npx -y @measure-events/mcp
Add to your Cursor or Claude Code config:
{
"mcpServers": {
"measure": {
"command": "npx",
"args": ["-y", "@measure-events/mcp"],
"env": {
"MEASURE_API_KEY": "your_api_key"
}
}
}
}
With this configured, you can ask your AI assistant questions directly in your editor:
“What Remix routes are getting the most traffic?” “How many checkout_complete events fired this week?” “What’s the top referrer for my app?”
No other analytics tool has native MCP support. For developers building Remix apps with AI-assisted workflows in 2026, this makes your analytics data part of your development context instead of a separate dashboard tab.
Getting Started
- Sign up at lets.measure.events/sign-up
- Create a site and get your site key
- Add the script to
root.tsxwith the<script defer>tag - Add the
Analyticscomponent withuseLocationfor SPA route tracking - Create the
/api/trackresource route for server-side events - Optional: install the MCP server for AI-powered queries
Total setup: under 15 minutes including the server-side resource route. Bytes added to your client bundle: 0.
Ready to see accurate analytics?
No cookies. No consent banners. No personal data. $29/mo with a 14-day free trial.
Start free trial →