supapulsedocs
Integration guidesReact Router

React Router

Add Supapulse to a React Router single-page app, with automatic pageviews, events and checkout attribution.

Install

npm install @supapulse/core @supapulse/react

Wrap the app

src/main.tsx
import { SupapulseProvider } from '@supapulse/react';
import { createRoot } from 'react-dom/client';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import { routes } from './routes';

const router = createBrowserRouter(routes);
const config = { apiToken: import.meta.env.VITE_SUPAPULSE_TOKEN };

createRoot(document.getElementById('root')!).render(
  <SupapulseProvider config={config}>
    <RouterProvider router={router} />
  </SupapulseProvider>,
);

Keep the provider above the router so it isn't remounted.

Pageviews

React Router navigates with history.pushState, which the tracker follows. Every route change is a pageview without extra code.

To control pageviews yourself, for example to send a cleaned URL, turn them off and use the hook inside the router:

const config = { apiToken: import.meta.env.VITE_SUPAPULSE_TOKEN, autoPageviews: false };

function RouteTracker() {
  const location = useLocation();
  usePageview({ trigger: location.pathname });
  return null;
}

Render <RouteTracker /> in your root layout route.

Events

import { useEvent } from '@supapulse/react';

export function PricingPage() {
  const trackEvent = useEvent();
  return <button onClick={() => trackEvent('plan_selected', { plan: 'growth' })}>Choose Growth</button>;
}

Checkout

import { useSupapulse } from '@supapulse/react';

export function BuyButton({ priceId }: { priceId: string }) {
  const supapulse = useSupapulse();

  async function buy() {
    const response = await fetch('/api/checkout', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        priceId,
        fingerprint: await supapulse.getVisitorFingerprint(),
        sessionId: supapulse.getSessionId(),
      }),
    });
    window.location.href = (await response.json()).url;
  }

  return <button onClick={buy}>Buy</button>;
}

Your backend stores the fingerprint and session ID in the payment's metadata, next to your user ID. With Stripe, Supapulse reads them from its own webhook. See the Stripe guide; other processors are in Payment webhooks.

Troubleshooting

  • Two pageviews per navigation. Some setups call replaceState right after pushState (for example, scroll restoration or search-param syncing). Use autoPageviews: false with the hook above.
  • Hash routers. With createHashRouter the route lives after #, and Supapulse records the URL's path, so every page shows up as /. Use autoPageviews: false and send the route as the URL yourself: supapulse.pageview({ url: location.origin + location.pathname }) with React Router's location.