Integration

Product analytics for React

Pug’s Web SDK is plain framework-agnostic JavaScript, so it works in any React app: Vite, Create React App, or anything else. Call init() once where your app boots, before the router mounts, and autocapture takes over.

Pug Web SDK: one SDK for the browser @pug-sh/browser Open source, AGPL-3.0

Setup

Add Pug to React

Four steps, start to first insight. Everything below uses the Web SDK exactly as it ships. There is no React wrapper in between.

  1. Install the Web SDK

    No provider package, no hook library, no peer dependency on React. The bundler build ships compiled JavaScript and type definitions from the package root.

    npm install @pug-sh/browser
    # or: pnpm add @pug-sh/browser · yarn add @pug-sh/browser · bun add @pug-sh/browser
  2. Initialize at your entry point

    Call init() once, at the top of the module that boots your app: before createRoot() and before your router mounts, so the first page view is recorded and the History wrappers are in place for the first navigation.

    src/main.tsx
    import { StrictMode } from 'react'
    import { createRoot } from 'react-dom/client'
    import { init } from '@pug-sh/browser'
    import App from './App'
    
    init(import.meta.env.VITE_PUG_PROJECT_ID, {
      apiKey: import.meta.env.VITE_PUG_PUBLIC_KEY,
    })
    
    createRoot(document.getElementById('root')!).render(
      <StrictMode>
        <App />
      </StrictMode>,
    )
  3. Identify on sign-in, reset on sign-out

    identify() attaches everything the anonymous visitor already did to their profile, and keeps it joined on their next device. reset() is its counterpart: it clears the identity for a logout or account switch so the next person on a shared machine does not inherit it.

    src/auth.ts
    import { identify, reset } from '@pug-sh/browser'
    
    export async function onSignIn(user: { id: string; email: string; plan: string }) {
      await identify(user.id, { email: user.email, plan: user.plan })
    }
    
    export function onSignOut() {
      reset()
    }
  4. Track the events autocapture can’t name

    Autocapture knows a click happened; it does not know it was a trial upgrade. Name the moments your funnels are built from: the well-known kinds are generated from the event registry, so a spelling that matches gets first-class treatment in the dashboard.

    src/components/UpgradeButton.tsx
    import { track } from '@pug-sh/browser'
    
    export function UpgradeButton({ plan }: { plan: string }) {
      return (
        <button onClick={() => track('subscription_started', { plan })}>
          Upgrade
        </button>
      )
    }
Framework notes

What to watch for in React

The specifics that decide whether the numbers you see are the numbers that happened.

Client-side routing is already covered

React Router, TanStack Router, and anything else built on the History API navigate through pushState/replaceState. The SDK wraps both, plus popstate, so a route change in a React SPA sends a page_view with no extra wiring.

StrictMode double-invocation is safe

A second init() no-ops with a console warning rather than installing a second set of listeners, so the development-only double render does not double-count. Calling it at module scope rather than in an effect avoids the warning entirely.

Autocapture is an allowlist, not switches

Passing autoCapture: { pageView: true, click: true } enables exactly those two: every omitted tracker stays off. There is no "everything except one" form, which is why the type rejects an explicit false.

Cookieless until someone answers

The default consent state writes no identifier to the device: events still flow, attributed to a server-derived identity that rotates daily. Wire a banner to optInTracking() / optOutTracking() when you want the full-identity version.

What you get

Product analytics in your React app

  • Autocapture, six trackers

    Page views, clicks, scrolls, form starts and submits, rage clicks, and dead clicks: all on after one init(), or narrowed with a per-listener allowlist.

  • One profile per person

    identify() merges the anonymous session into the signed-in profile and keeps it joined across devices and later visits.

  • Your own events

    track() takes a name and typed properties, with a generated list of well-known event kinds so your names match what the insights expect.

  • Every insight type

    Trends, funnels, retention cohorts, segmentation, user flows, and top-K run over the same events, no separate instrumentation per report.

FAQ

Pug + React: common questions

Do I need a React-specific package?

No. There is one Pug Web SDK and it is framework-agnostic. In React you import @pug-sh/browser and call init() once at startup, no provider or hook library required.

Where should I call init()?

At your app’s entry point (e.g. main.tsx), at module scope so it runs once before the tree renders and before your router mounts. Autocapture then records page views, clicks, scrolls, and form interactions; call identify() on sign-in and track() for custom events.

Does it support single-page-app route changes?

Yes. The SDK wraps history.pushState and history.replaceState and listens for popstate, so any router built on the History API (React Router, TanStack Router, and the rest) produces a page_view on every client-side navigation without extra wiring.

Will React StrictMode double-count events?

No. init() no-ops with a warning if it is called a second time, so StrictMode’s development double-invocation does not install duplicate listeners. Track calls inside effects follow ordinary React rules: the usual guards apply there as with any side effect.

Can I keep form fields and URLs out of the payload?

Yes. Form autocapture records metadata only (form id, name, and the action on submit), never field values. Known-sensitive query and fragment params are redacted from URLs by default, and beforeSend gives you a synchronous hook to mask, rewrite, or drop any event before it leaves the browser.

Add Pug to your React app.

Open-source product analytics with unified profiles. Self-host under AGPL-3.0, or use the free cloud during open beta.

Questions? Email hello@pug.sh