Skip to main content

Next.js example

Wire up one canonical anonymous id and fan a single identify() call out to Segment, LaunchDarkly, and FullStory — from a Next.js App Router app. See Architecture for the concepts behind IdentityProvider/IdentityClient.

1. Install

npm install @idhub/identity-core @idhub/identity-adapter-nextjs \
@idhub/identity-provider-segment @idhub/identity-provider-launchdarkly @idhub/identity-provider-fullstory

2. middleware.ts — guarantee the anonymous id before first render

// middleware.ts
import { identityMiddleware } from '@idhub/identity-adapter-nextjs/middleware'

export const middleware = identityMiddleware()
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] }

This is the whole file for apps with no middleware logic of their own. It guarantees the _anon_id cookie exists — and is visible to Server Components rendering this same request — on the very first hit, cold cache included.

If you already have middleware logic (auth redirects, rewrites), compose with withIdentity instead, which hands back the pieces rather than wrapping your handler:

// middleware.ts
import { withIdentity, applyAnonymousIdCookie } from '@idhub/identity-adapter-nextjs/middleware'
import { NextResponse, type NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
const { response, anonymousId } = withIdentity(request)
response.headers.set('x-anon-id', anonymousId)
return response
}

export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] }

3. app/layout.tsx — read the id on the server, mount the client providers

// app/layout.tsx — Server Component
import { getAnonymousIdFromCookies } from '@idhub/identity-adapter-nextjs/server'
import { cookies } from 'next/headers'
import { Providers } from './providers'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
const anonymousId = getAnonymousIdFromCookies(await cookies())
return (
<html lang="en">
<body>
<Providers initialAnonymousId={anonymousId}>{children}</Providers>
</body>
</html>
)
}
// app/providers.tsx
'use client'
import { IdentityProvider } from '@idhub/identity-adapter-nextjs'
import { segmentProvider } from '@idhub/identity-provider-segment'
import { launchDarklyProvider } from '@idhub/identity-provider-launchdarkly'
import { fullStoryProvider } from '@idhub/identity-provider-fullstory'
import { analytics } from '../lib/segment-client' // your own analytics-node / analytics.js instance

export function Providers({
initialAnonymousId,
children,
}: {
initialAnonymousId?: string
children: React.ReactNode
}) {
return (
<IdentityProvider
providers={[
segmentProvider({ client: analytics, writeKey: process.env.NEXT_PUBLIC_SEGMENT_WRITE_KEY! }),
launchDarklyProvider({ clientSideId: process.env.NEXT_PUBLIC_LD_CLIENT_ID! }),
fullStoryProvider({ orgId: process.env.NEXT_PUBLIC_FULLSTORY_ORG_ID! }),
]}
initialAnonymousId={initialAnonymousId}
>
{children}
</IdentityProvider>
)
}

initialAnonymousId is passed explicitly from the server read in step 3 rather than derived from document.cookie on first render — <IdentityProvider> is a Client Component that Next.js still server-renders, and there is no document on the server, so a cookie-derived initial value would mint a second id that disagrees with the one the browser holds. Passing the server value down keeps the SSR pass and the hydration pass in agreement.

4. Call identify() at login

'use client'
import { useIdentity } from '@idhub/identity-adapter-nextjs'

export function LoginButton() {
const { state, identify, reset } = useIdentity()
return (
<button onClick={() => identify('user-123', { plan: 'pro' })}>
{state.status === 'identified' ? `Hi ${state.userId}` : 'Log in'}
</button>
)
}

One call fans out to onIdentify on all three providers — Segment (identify(userId, traits)), LaunchDarkly (multi-kind device + user context, replacing the removed alias()), and FullStory (setIdentity, which auto-merges the anonymous session). Call reset() on logout; it issues a brand-new anonymous id rather than reusing the pre-login one.

That's the full integration: one middleware export, one provider composition in app/layout.tsx/app/providers.tsx, and useIdentity() anywhere you need identify/reset/track/page.

Building an Angular app instead? See the Angular example.