> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wendung.app/llms.txt
> Use this file to discover all available pages before exploring further.

# React SPA (Vite, CRA, React Router)

> Add the SDK to a React single-page app: initialize once at startup, identify users after login, and turn on pageviews for React Router navigations.

In a single-page app the page never fully reloads, so you initialize the SDK once at startup and keep that instance alive for the whole visit. Logins and logouts become SDK calls; route changes are picked up for you once pageview tracking is on.

This guide uses React with Vite and React Router as the primary example. The same patterns apply to Create React App or any other React setup; only the environment variable prefix changes (`VITE_` vs `REACT_APP_`).

## Setup

<Steps>
  <Step title="Install the SDK">
    Add `@wendung/sdk` to your project.

    <CodeGroup>
      ```bash npm theme={null}
      npm install @wendung/sdk
      ```

      ```bash pnpm theme={null}
      pnpm add @wendung/sdk
      ```

      ```bash yarn theme={null}
      yarn add @wendung/sdk
      ```
    </CodeGroup>

    Then add your publishable key to `.env`:

    ```bash .env theme={null}
    VITE_WENDUNG_KEY=pk_...
    ```

    Your key is under [Settings → API Keys](/help/settings#api-keys) in the dashboard.
  </Step>

  <Step title="Initialize at app startup">
    Call `init()` at your entry point, before rendering. This starts the flush loop and registers the page-unload listeners.

    ```tsx src/main.tsx theme={null}
    import { StrictMode } from 'react'
    import { createRoot } from 'react-dom/client'
    import { Wendung } from '@wendung/sdk'
    import { App } from './App'

    Wendung.init({
      apiKey: import.meta.env.VITE_WENDUNG_KEY,
    })

    createRoot(document.getElementById('root')!).render(
      <StrictMode>
        <App />
      </StrictMode>,
    )
    ```

    <Note>
      `init()` is idempotent. React Strict Mode double-invokes effects in development, and Vite's hot module replacement can re-run your entry file. Both are handled, with no duplicate listeners or flushes.
    </Note>
  </Step>

  <Step title="Track funnel steps">
    Call `step()` whenever a user completes a step in one of your funnels. The name should match a step defined in the dashboard. The second argument is an optional object of properties.

    ```tsx src/components/SignupForm.tsx theme={null}
    import { Wendung } from '@wendung/sdk'

    export function SignupForm() {
      async function handleSubmit() {
        // ...your signup logic

        Wendung.step('signup_completed', {
          plan: 'pro',
          source: 'google_ads',
        })
      }

      return <button onClick={handleSubmit}>Create account</button>
    }
    ```
  </Step>

  <Step title="Identify users after login">
    Once you know who the user is, call `identify()` with a stable user ID and any traits you want on the events that follow.

    ```ts theme={null}
    import { Wendung } from '@wendung/sdk'

    async function onLoginSuccess(user: {
      id: string
      email: string
      plan: string
    }) {
      Wendung.identify(user.id, {
        email: user.email,
        plan: user.plan,
      })
    }
    ```

    The identity is saved in `localStorage` and restored by `init()`, so a reload keeps the user identified. Each `identify()` call replaces the identity and traits wholesale, with no merging, and `reset()` clears it. Re-identify anyway once your app has checked its own session, so traits stay current.
  </Step>

  <Step title="Turn on pageview tracking">
    SPAs do not fire page loads on navigation, and you do not need to emit anything yourself: with `trackPageviews: true` the SDK records the initial load and every React Router navigation.

    ```ts src/main.tsx theme={null}
    Wendung.init({
      apiKey: import.meta.env.VITE_WENDUNG_KEY,
      trackPageviews: true,
    })
    ```

    For aggregate pageviews while consent is pending, use `trackPageviews: 'auto'` and pass the current `consent` state. Call `Wendung.setConsent('granted')` to switch to full analytics, or `'denied'` to stop collection. See the [consent guide](/guides/consent) for saving and restoring the choice.

    Register the hostname under **Sites** in the dashboard to see the traffic. If your router changes the URL without the history API, call `page()` after each navigation instead. Pageviews use a separate monthly allowance from events; see the [web analytics guide](/guides/web-analytics).

    <Tip>
      Keep funnel steps and pageviews apart. `step()` is for actions like `signup_completed` or `checkout_started`; pageviews are collected for you and reported per site.
    </Tip>
  </Step>

  <Step title="Handle logout">
    Call `reset()` when the user signs out. It flushes pending events under the old identity, clears the user ID and traits, and starts a new session.

    ```ts theme={null}
    import { Wendung } from '@wendung/sdk'

    async function onLogout() {
      // ...your sign-out logic

      Wendung.reset()
    }
    ```
  </Step>
</Steps>

## Advanced: teardown in micro-frontends

Most SPAs never call `destroy()`. The SDK lives as long as the page does, and the queue drain on page hide handles the final send.

The exception is a micro-frontend shell that loads and unloads sub-applications at runtime. There, call `destroy()` in your cleanup hook to stop the flush loop, remove the listeners, and send one last batch:

```tsx theme={null}
import { Wendung } from '@wendung/sdk'

Wendung.init({ apiKey: import.meta.env.VITE_WENDUNG_KEY })

const root = createRoot(document.getElementById('root')!)
root.render(<App />)

export function unmount() {
  Wendung.destroy()
  root.unmount()
}
```

After `destroy()`, call `init()` again before using any other method.

## What's next

<CardGroup cols={2}>
  <Card title="Create your first funnel" icon="chart-line" href="/guides/funnels">
    Turn your tracked step names into a funnel in the dashboard.
  </Card>

  <Card title="API reference" icon="code" href="/api-reference/init">
    Every option accepted by `init()`, `identify()`, `step()`, and `reset()`.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.