Skip to main content
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

1

Install the SDK

Add @wendung/sdk to your project.
Then add your publishable key to .env:
.env
Your key is under Settings → API Keys in the dashboard.
2

Initialize at app startup

Call init() at your entry point, before rendering. This starts the flush loop and registers the page-unload listeners.
src/main.tsx
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.
3

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.
src/components/SignupForm.tsx
4

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.
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.
5

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.
src/main.tsx
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 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.
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.
6

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.

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:
After destroy(), call init() again before using any other method.

What’s next

Create your first funnel

Turn your tracked step names into a funnel in the dashboard.

API reference

Every option accepted by init(), identify(), step(), and reset().