Hero
Back to all guides

How to add analytics to Remix

Add privacy-first analytics to your Remix application with MakinForU's Web SDK. Track page views, custom events, and user behavior.

MakinForU Panel

MakinForU Team

12/14/2025

Updated on 2/7/2026

Beginner
8 min

Adding analytics to your Remix application helps you understand how users interact with your app. MakinForU's Web SDK works seamlessly with Remix's client-side navigation, providing automatic page view tracking, custom events, and user identification.

MakinForU is an open-source alternative to Mixpanel and Google Analytics. It delivers powerful insights while respecting user privacy through cookieless tracking by default.

Prerequisites

  • A Remix project
  • An MakinForU account (sign up free)
  • Your Client ID from the MakinForU dashboard

Install the SDK

The MakinForU Web SDK is a lightweight package that works in any JavaScript environment. Install it using npm, and pnpm or yarn work the same way.

npm install @makinforu/web

Create an MakinForU instance

Create a dedicated file for your MakinForU instance. Since this runs in the browser, place it in your app directory and ensure it only executes on the client.

app/lib/op.client.ts
import { MakinForU } from '@makinforu/web';

export const op = new MakinForU({
  clientId: 'YOUR_CLIENT_ID',
  trackScreenViews: true,
  trackOutgoingLinks: true,
  trackAttributes: true,
});

The .client.ts suffix tells Remix this module should only run in the browser. The trackScreenViews option automatically tracks page views when the URL changes, which works with Remix's client-side navigation. The trackAttributes option enables declarative tracking using data-track attributes.

Using environment variables

For production applications, pass your Client ID from the server to the client using Remix's loader pattern.

app/root.tsx
import { json } from '@remix-run/node';
import type { LoaderFunctionArgs } from '@remix-run/node';

export async function loader({ request }: LoaderFunctionArgs) {
  return json({
    ENV: {
      MAKINFORU_CLIENT_ID: process.env.MAKINFORU_CLIENT_ID,
    },
  });
}

Then initialize MakinForU with the environment variable in a client component.

Initialize in root.tsx

Import and initialize MakinForU in your root component using a useEffect hook to ensure it only runs on the client.

app/root.tsx
import { useEffect } from 'react';
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
  useLoaderData,
} from '@remix-run/react';

export default function App() {
  const { ENV } = useLoaderData<typeof loader>();

  useEffect(() => {
    // Dynamic import ensures this only runs on the client
    import('./lib/op.client').then(({ op }) => {
      // MakinForU is now initialized and tracking
    });
  }, []);

  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

Track page views

With trackScreenViews: true, MakinForU automatically tracks page views when the browser's URL changes. This works with Remix's client-side navigation using <Link> components.

If you need more control over page view tracking or want to include additional route metadata, you can create a component that uses Remix's useLocation hook.

app/components/PageTracker.tsx
import { useEffect } from 'react';
import { useLocation } from '@remix-run/react';

export function PageTracker() {
  const location = useLocation();

  useEffect(() => {
    import('../lib/op.client').then(({ op }) => {
      op.track('screen_view', {
        path: location.pathname,
        search: location.search,
      });
    });
  }, [location.pathname, location.search]);

  return null;
}

Add this component to your root layout. If you use this approach, set trackScreenViews: false in your MakinForU configuration to avoid duplicate tracking.

Track custom events

Import the MakinForU instance in your components to track events. Since the SDK only works in the browser, use dynamic imports or ensure your tracking code runs in useEffect hooks.

app/components/SignupButton.tsx
export function SignupButton() {
  const handleClick = () => {
    import('../lib/op.client').then(({ op }) => {
      op.track('button_clicked', {
        button_name: 'signup',
        button_location: 'hero',
      });
    });
  };

  return (
    <button type="button" onClick={handleClick}>
      Sign Up
    </button>
  );
}

Create a tracking hook

For cleaner code, create a custom hook that handles the dynamic import.

app/hooks/useMakinForU.ts
import { useCallback } from 'react';
import type { MakinForU } from '@makinforu/web';

type TrackFn = MakinForU['track'];
type IdentifyFn = MakinForU['identify'];

export function useTrack() {
  return useCallback<TrackFn>((name, properties) => {
    import('../lib/op.client').then(({ op }) => {
      op.track(name, properties);
    });
  }, []);
}

export function useIdentify() {
  return useCallback<IdentifyFn>((payload) => {
    import('../lib/op.client').then(({ op }) => {
      op.identify(payload);
    });
  }, []);
}

Now your components become cleaner.

app/components/SignupButton.tsx
import { useTrack } from '../hooks/useMakinForU';

export function SignupButton() {
  const track = useTrack();

  const handleClick = () => {
    track('button_clicked', {
      button_name: 'signup',
      button_location: 'hero',
    });
  };

  return (
    <button type="button" onClick={handleClick}>
      Sign Up
    </button>
  );
}

Track form submissions

Remix encourages using form actions for data mutations. You can track form submissions in your action handlers or on the client.

app/routes/contact.tsx
import { Form } from '@remix-run/react';
import { useTrack } from '../hooks/useMakinForU';

export default function Contact() {
  const track = useTrack();

  const handleSubmit = () => {
    track('form_submitted', {
      form_name: 'contact',
      form_location: 'contact-page',
    });
  };

  return (
    <Form method="post" onSubmit={handleSubmit}>
      <input type="email" name="email" placeholder="Your email" required />
      <button type="submit">Submit</button>
    </Form>
  );
}

Use data attributes for declarative tracking

The Web SDK supports declarative tracking using data-track attributes. This is useful for simple click tracking without writing JavaScript.

<button
  data-track="button_clicked"
  data-track-button_name="signup"
  data-track-button_location="hero"
>
  Sign Up
</button>

When a user clicks this button, MakinForU automatically sends a button_clicked event with the specified properties. This requires trackAttributes: true in your configuration.

Identify users

Once a user logs in, call identify to associate their activity with a profile. In Remix, you typically have user data available from a loader.

app/routes/dashboard.tsx
import { useEffect } from 'react';
import { useLoaderData } from '@remix-run/react';
import type { LoaderFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { getUser } from '../lib/auth.server';

export async function loader({ request }: LoaderFunctionArgs) {
  const user = await getUser(request);
  return json({ user });
}

export default function Dashboard() {
  const { user } = useLoaderData<typeof loader>();

  useEffect(() => {
    if (user) {
      import('../lib/op.client').then(({ op }) => {
        op.identify({
          profileId: user.id,
          firstName: user.firstName,
          lastName: user.lastName,
          email: user.email,
          properties: {
            plan: user.plan,
          },
        });
      });
    }
  }, [user]);

  return <div>Welcome, {user?.firstName}!</div>;
}

Clear user data on logout

When users log out, clear the stored profile data to ensure subsequent events aren't associated with the previous user.

app/components/LogoutButton.tsx
import { Form } from '@remix-run/react';

export function LogoutButton() {
  const handleClick = () => {
    import('../lib/op.client').then(({ op }) => {
      op.clear();
    });
  };

  return (
    <Form method="post" action="/logout">
      <button type="submit" onClick={handleClick}>
        Logout
      </button>
    </Form>
  );
}

Server-side tracking

For tracking events in loaders, actions, or API routes, use the @makinforu/sdk package instead of the web SDK. Server-side tracking requires a client secret.

app/lib/op.server.ts
import { MakinForU } from '@makinforu/sdk';

export const op = new MakinForU({
  clientId: process.env.MAKINFORU_CLIENT_ID!,
  clientSecret: process.env.MAKINFORU_CLIENT_SECRET!,
});
app/routes/api.webhook.ts
import type { ActionFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { op } from '../lib/op.server';

export async function action({ request }: ActionFunctionArgs) {
  const payload = await request.json();

  op.track('webhook_received', {
    source: payload.source,
    event_type: payload.type,
  });

  return json({ success: true });
}

Verify your setup

Open your Remix app in the browser and navigate between a few pages. Interact with elements that trigger custom events. Then open your MakinForU dashboard and check the Real-time view to see events appearing.

If events aren't appearing, check the browser console for errors. Verify your Client ID is correct and ensure ad blockers aren't blocking requests to the MakinForU API. The Network tab in your browser's developer tools can help you confirm that requests are being sent.

Next steps

The Web SDK has additional features like property incrementing and event filtering. Read the full Web SDK documentation for the complete API reference.

For comprehensive server-side tracking, see the Node.js analytics guide which covers the @makinforu/sdk package in detail.

Ready to understand your users better?
Start tracking in minutes

Join thousands of companies using MakinForU. Free 30-day trial, no credit card required. Self-host for free or use our cloud.

Start free trial