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.