Next.js Integration
Use a client wrapper in App Router, then mount FeatureDropProvider once near the root.
App Router pattern (app/)
app/providers.tsx
'use client'
import { LocalStorageAdapter } from 'featuredrop'
import { FeatureDropProvider } from 'featuredrop/react'
import type { FeatureManifest } from 'featuredrop'
export function Providers({
children,
manifest,
appVersion
}: {
children: React.ReactNode
manifest: FeatureManifest
appVersion: string
}) {
return (
<FeatureDropProvider
manifest={manifest}
storage={new LocalStorageAdapter({ prefix: 'featuredrop' })}
appVersion={appVersion}
>
{children}
</FeatureDropProvider>
)
}app/layout.tsx
import { Providers } from './providers'
import { manifest } from '@/featuredrop.manifest'
export default function RootLayout({ children }: { children: React.ReactNode }) {
const appVersion = process.env.NEXT_PUBLIC_APP_VERSION ?? '0.0.0'
return (
<html lang="en">
<body>
<Providers manifest={manifest} appVersion={appVersion}>
{children}
</Providers>
</body>
</html>
)
}Where to mount UI components
Mount long-lived UI once (for example in your root layout shell):
<ChangelogWidget />- global
<Toast /> <Tour />if it should persist across route transitions
Using remote state in Next.js
If you persist dismissals remotely, use RemoteAdapter with an API route:
import { RemoteAdapter } from 'featuredrop/adapters'
new RemoteAdapter({ url: '/api/featuredrop' })Expected endpoints:
GET /api/featuredropGET /api/featuredrop/statePOST /api/featuredrop/dismissPOST /api/featuredrop/dismiss-batch(optional, for burst dismiss traffic)POST /api/featuredrop/dismiss-all
Minimal App Router handlers:
app/api/featuredrop/dismiss-batch/route.ts
import { NextResponse } from 'next/server'
export async function POST(req: Request) {
const body = (await req.json()) as { featureIds?: string[] }
const featureIds = Array.isArray(body.featureIds) ? body.featureIds.filter(Boolean) : []
if (!featureIds.length) return NextResponse.json({ ok: true })
// Persist to your DB by user/session.
await saveDismissBatch(featureIds)
return NextResponse.json({ ok: true })
}app/api/featuredrop/dismiss/route.ts
import { NextResponse } from 'next/server'
export async function POST(req: Request) {
const body = (await req.json()) as { featureId?: string }
if (!body.featureId) return NextResponse.json({ error: 'featureId required' }, { status: 400 })
await saveDismiss(body.featureId)
return NextResponse.json({ ok: true })
}⚠️
Do not create a new storage adapter instance inside frequently re-rendering leaf components. Keep adapter/provider setup stable at app-shell level.