Next.js: @posthog/next

Pre-release

@posthog/next is currently in pre-release. The API may change before the stable release. For production apps, see the standard Next.js setup guide.

@posthog/next is a unified package for integrating PostHog into your Next.js app. It provides a simplified interface that handles common patterns out of the box:

  • Synchronized identity across client and server, so both sides share the same user automatically
  • Server-side feature flag bootstrapping so hooks return real values immediately with no flicker
  • Eager client initialization ensures PostHog is always available when your components render
  • Built-in API proxy that routes SDK traffic through your domain to avoid ad blockers
  • Automatic event flushing with no manual shutdown() or flush() calls needed

This guide covers both the App Router and Pages Router. You'll need a PostHog instance (Cloud or self-hosted) and a Next.js 13.0.0+ application.

  1. Install @posthog/next

    Required
    npm install --save @posthog/next
  2. Add your environment variables

    Required

    Add these to your .env.local file and to your hosting provider (e.g. Vercel, Netlify, AWS). You can find your project token in your project settings.

    .env.local
    NEXT_PUBLIC_POSTHOG_KEY=<ph_project_api_key>
    NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com

    NEXT_PUBLIC_POSTHOG_HOST is optional and defaults to https://us.i.posthog.com.

  3. Add the middleware

    Recommended

    The middleware seeds an identity cookie on first visit so client and server share the same user, and optionally proxies API requests through your domain.

    middleware.ts
    import { postHogMiddleware } from '@posthog/next'
    export default postHogMiddleware({ proxy: true })
    export const config = {
    matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
    }

    Setting proxy: true routes PostHog API calls through your domain at /ingest, which helps avoid ad blockers. You can customize the path prefix by passing proxy: { pathPrefix: '/analytics' } instead.

    Note: Middleware requires a server runtime and is not available with output: 'export' (fully static sites). If you can't use middleware, you can set up a reverse proxy separately to route traffic through your domain.

  4. Wrap your app with PostHogProvider

    Required

    Add PostHogProvider and PostHogPageView to your root layout:

    app/layout.tsx
    import { PostHogProvider, PostHogPageView } from '@posthog/next'
    export default function RootLayout({ children }: { children: React.ReactNode }) {
    return (
    <html lang="en">
    <body>
    <PostHogProvider clientOptions={{ api_host: '/ingest' }} bootstrapFlags>
    <PostHogPageView />
    {children}
    </PostHogProvider>
    </body>
    </html>
    )
    }

    PostHogProvider is a React Server Component. When bootstrapFlags is enabled, it evaluates feature flags server-side and passes the results to the client so hooks return real values immediately.

    Note: Enabling bootstrapFlags opts the route into dynamic rendering (incompatible with static generation / ISR). Without it, the provider does not call any dynamic Next.js APIs and is safe for static pages.

  5. Verify events are captured

    Checkpoint
    Confirm that you can see events in your PostHog project

    Visit your app and click around. Within a minute or two, you should see $pageview and autocaptured events (like clicks) appear in the activity tab.

  6. Access PostHog in client components

    Required

    All hooks must be used in client components ('use client').

    Capture events:

    TSX
    'use client'
    import { usePostHog } from '@posthog/next'
    export function TrackButton() {
    const posthog = usePostHog()
    return <button onClick={() => posthog.capture('button_clicked')}>Track</button>
    }

    Feature Flags:

    TSX
    'use client'
    import { useFeatureFlag } from '@posthog/next'
    export function MyComponent() {
    const flag = useFeatureFlag('new-feature')
    return flag?.enabled ? <NewFeature /> : <OldFeature />
    }

    Conditional rendering with PostHogFeature:

    TSX
    'use client'
    import { PostHogFeature } from '@posthog/next'
    export function NewBanner() {
    return (
    <PostHogFeature flag="show-banner" match={true}>
    <div>New feature available!</div>
    </PostHogFeature>
    )
    }

    You can also read the full posthog-js documentation for all the usable functions.

  7. Identify your user

    Recommended

    Call posthog.identify() on the client after login to link events to a known user. This connects event captures, session replays, LLM traces, and Error Tracking to the same person across client and server.

    TSX
    'use client'
    import { usePostHog } from '@posthog/next'
    export function LoginButton() {
    const posthog = usePostHog()
    const handleLogin = async () => {
    const user = await signIn()
    posthog.identify(user.id, {
    email: user.email,
    name: user.name,
    })
    }
    return <button onClick={handleLogin}>Log in</button>
    }

    See our guide on identifying users for more details.

  8. Use PostHog on the server

    Recommended

    Use getPostHog() in server components, route handlers, and server actions to capture events and evaluate feature flags. The client is automatically scoped to the current user via the identity cookie set by the middleware.

    TSX
    import { getPostHog } from '@posthog/next'
    export default async function DashboardPage() {
    const posthog = await getPostHog()
    const flags = await posthog.getAllFlags()
    posthog.capture({ event: 'dashboard_viewed' })
    return (
    <div>{flags['new-dashboard'] ? <NewDashboard /> : <OldDashboard />}</div>
    )
    }

    Note: getPostHog() calls cookies() internally, which opts the route into dynamic rendering. Pages using it cannot be statically generated.

    Event flushing: On Vercel, @posthog/next auto-detects waitUntil from @vercel/functions so events are flushed after the response is sent without blocking it. No manual shutdown() call is needed. In other environments, you can pass a custom waitUntil via serverOptions on the PostHogProvider, or events will be batched and flushed normally.

Community questions

Was this page useful?

Questions about this page? or post a community question.