React Router Integration#

React Router is the most common SPA setup. Pulsora works out of the box—this guide stitches together the provider, pageview tracking, and event hooks.

Install Packages#

npm install @pulsora/react @pulsora/core

Wrap Your App#

// src/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { PulsoraProvider } from '@pulsora/react';
import { App } from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <PulsoraProvider config={{ apiToken: import.meta.env.VITE_PULSORA_TOKEN }}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </PulsoraProvider>
  </React.StrictMode>,
);

Track Pageviews#

Create a bridge component that listens to router changes and triggers usePageview():

// src/analytics/pageviews.tsx
import { useLocation } from 'react-router-dom';
import { usePageview } from '@pulsora/react';

export function PageviewTracker() {
  const location = useLocation();
  usePageview({ trigger: location.pathname + location.search });
  return null;
}

Use Hooks in Routes#

// src/App.tsx
import { Routes, Route } from 'react-router-dom';
import { PageviewTracker } from './analytics/pageviews';
import { useEvent } from '@pulsora/react';

function Home() {
  const trackEvent = useEvent();
  return (
    <button
      className="btn btn-primary"
      onClick={() => trackEvent('cta_click', { plan: 'starter' })}
    >
      Start free trial
    </button>
  );
}

export function App() {
  return (
    <>
      <PageviewTracker />
      <Routes>
        <Route path="/" element={<Home />} />
      </Routes>
    </>
  );
}

Capturing Checkout Metadata#

import { usePulsora } from '@pulsora/react';

function CheckoutButton() {
  const pulsora = usePulsora();

  const handleClick = async () => {
    const fingerprint = await pulsora?.getVisitorFingerprint();
    const sessionId = pulsora?.getSessionId();

    await fetch('/api/create-checkout', {
      method: 'POST',
      body: JSON.stringify({
        priceId: 'price_123',
        visitor_fingerprint: fingerprint,
        session_id: sessionId,
      }),
    });
  };

  return <button onClick={handleClick}>Buy now</button>;
}

Revenue Webhook (Node.js)#

import { Revenue } from '@pulsora/revenue';

const revenue = new Revenue({
  apiToken: process.env.PULSORA_SECRET!,
});

app.post('/webhooks/stripe', async (req, res) => {
  const session = req.body.data.object;

  await revenue.track({
    visitor_fingerprint: session.metadata.visitor_fingerprint,
    session_id: session.metadata.session_id,
    customer_id: session.customer,
    amount: session.amount_total / 100,
    currency: session.currency.toUpperCase(),
    transaction_id: session.id,
    payment_processor: 'stripe',
    event_type: 'subscription',
    customer_subscription_id: session.subscription,
    is_recurring: true,
  });

  res.json({ received: true });
});

Troubleshooting#

  • Multiple pageviews per route — Ensure PageviewTracker renders once at the top level.
  • Null pulsora instance — Guard usePulsora() return values until initialization completes.
  • Revenue errors — Double-check that checkout metadata contains visitor_fingerprint and session_id.

Need Stripe-specific steps? Jump to the Stripe guide →