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

# Next.js (App Router)

> Add the SDK to a Next.js App Router project: initialize it in a client provider, track funnel steps, identify users, and turn on pageviews.

The SDK runs entirely in the browser, so in Next.js you initialize it inside a client component and wrap your app in a provider. This guide covers the App Router setup. If you're on Pages Router, see the note at the bottom.

## 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.local`:

    ```bash .env.local theme={null}
    NEXT_PUBLIC_WENDUNG_KEY=pk_...
    ```

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

  <Step title="Create a provider component">
    Create `app/providers/wendung-provider.tsx`. It initializes the SDK once on mount and tears it down on unmount.

    ```tsx app/providers/wendung-provider.tsx theme={null}
    'use client'

    import { useEffect } from 'react'
    import { Wendung } from '@wendung/sdk'

    export function WendungProvider({ children }: { children: React.ReactNode }) {
      useEffect(() => {
        Wendung.init({
          apiKey: process.env.NEXT_PUBLIC_WENDUNG_KEY!,
        })

        return () => {
          Wendung.destroy()
        }
      }, [])

      return <>{children}</>
    }
    ```

    <Note>
      `init()` is idempotent. If an instance is already running, as happens under React Strict Mode or Fast Refresh, it is torn down before the new one starts, so there are no duplicate flushes or listeners.
    </Note>
  </Step>

  <Step title="Add the provider to your root layout">
    Wrap your app's `children` with `<WendungProvider>` in `app/layout.tsx`.

    ```tsx app/layout.tsx theme={null}
    import { WendungProvider } from './providers/wendung-provider'

    export default function RootLayout({
      children,
    }: {
      children: React.ReactNode
    }) {
      return (
        <html lang="en">
          <body>
            <WendungProvider>{children}</WendungProvider>
          </body>
        </html>
      )
    }
    ```

    `layout.tsx` can remain a server component. Only `WendungProvider` itself needs `'use client'`.
  </Step>

  <Step title="Track funnel steps">
    Call `step()` from any client component to record an event. The name should match a step in one of your funnels. The optional second argument is a plain object of properties.

    ```tsx app/components/signup-form.tsx theme={null}
    'use client'

    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">
    Identify a user as soon as you know who they are, usually right after login. The cleanest pattern is a small client component that reads the session and calls `identify()` once:

    ```tsx app/providers/wendung-identity.tsx theme={null}
    'use client'

    import { useEffect } from 'react'
    import { Wendung } from '@wendung/sdk'
    import { useSession } from '@/lib/auth'

    export function WendungIdentity() {
      const { data: session } = useSession()

      useEffect(() => {
        if (!session?.user) return

        Wendung.identify(session.user.id, {
          email: session.user.email,
          plan: session.user.plan,
        })
      }, [session?.user?.id])

      return null
    }
    ```

    Drop `<WendungIdentity />` inside `<WendungProvider>` in your layout. It runs once the session is available and re-identifies if the user changes.

    If your auth flow is fully client-side, you can also call `identify()` from your login handler. Use whichever fits your stack.
  </Step>

  <Step title="Turn on pageview tracking">
    Next.js App Router does not trigger a full page load between routes. With `trackPageviews: true` the SDK records the initial load and every client-side navigation, so route tracking needs no code of your own:

    ```tsx app/providers/wendung-provider.tsx theme={null}
    Wendung.init({
      apiKey: process.env.NEXT_PUBLIC_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. 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.

    ```tsx app/components/logout-button.tsx theme={null}
    'use client'

    import { Wendung } from '@wendung/sdk'

    export function LogoutButton() {
      async function handleLogout() {
        // ...your sign-out logic

        Wendung.reset()
      }

      return <button onClick={handleLogout}>Sign out</button>
    }
    ```
  </Step>
</Steps>

## Server Components and Server Actions

The SDK is browser-only. Don't call it from Server Components, Route Handlers, or Server Actions. Importing it there is harmless, but calling it is not a supported path: the browser lifecycle hooks never run, and a server runtime with `fetch` may still fire network requests. To record an event that results from a server action, either:

* Return a signal from the action and call `step()` in the client component that invoked it, or
* Call `step()` optimistically before invoking the action.

## Using Pages Router?

The setup is similar: initialize in `pages/_app.tsx` inside a `useEffect`, and use `router.events` from `next/router` for route-change tracking. A dedicated Pages Router guide is coming; for now, the [Quickstart](/quickstart) covers the core flow.

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