{"_id":"@avsbhq/next","_rev":"5-4a110ae2913ad172a0fa75f7c7d56cc6","name":"@avsbhq/next","dist-tags":{"latest":"1.1.1"},"versions":{"1.0.0":{"name":"@avsbhq/next","version":"1.0.0","_id":"@avsbhq/next@1.0.0","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/main1479/a-vs-b/tree/main/packages/avsb-next#readme","bugs":{"url":"https://github.com/main1479/a-vs-b/issues"},"dist":{"shasum":"2de2ab99c8df556bbd51f182d16fd8546086f892","tarball":"https://registry.npmjs.org/@avsbhq/next/-/next-1.0.0.tgz","fileCount":18,"integrity":"sha512-1t91aeqRj8rG2de+JIj5Vk7Di6nVXmR90gBnf1TIeVwvsrJrBM5427VqXe+b6sUwytNBAUga08poPTVMHkFNFg==","signatures":[{"sig":"MEUCICE3EVHA/Q0NUjYjK8umNbve1jxO4O6TL9CKbH/Vr2eAAiEAwedXXBZ0FeyE/ngkZ/xtpiaVggORVsp8XVLbMIt0imo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":54278},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./pages":{"types":"./dist/pages/getServerSideProps.d.ts","import":"./dist/pages/getServerSideProps.js","require":"./dist/pages/getServerSideProps.cjs"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js","require":"./dist/server.cjs"},"./middleware":{"types":"./dist/middleware.d.ts","import":"./dist/middleware.js","require":"./dist/middleware.cjs"}},"gitHead":"8cd6226e120fe67c198b2f1db0087cd38b4a4060","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","test:watch":"vitest"},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"repository":{"url":"git+https://github.com/main1479/a-vs-b.git","type":"git","directory":"packages/avsb-next"},"_npmVersion":"10.9.2","description":"Next.js 15+ integration for the [A vs B](https://app.avsb.cloud) platform.","directories":{},"_nodeVersion":"22.14.0","dependencies":{"@avsbhq/core":"1.0.0","@avsbhq/node":"1.0.0","@avsbhq/react":"1.0.0","@avsbhq/utils":"1.0.0","@avsbhq/browser":"1.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.2.1","tsup":"^8.0.0","react":"^19.0.0","vitest":"^3.0.0","typescript":"^5.5.0","@types/react":"^19.0.0"},"peerDependencies":{"next":">=15.0.0","react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/next_1.0.0_1780034362836_0.24678220686698382","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@avsbhq/next","version":"1.0.1","_id":"@avsbhq/next@1.0.1","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/main1479/a-vs-b/tree/main/packages/avsb-next#readme","bugs":{"url":"https://github.com/main1479/a-vs-b/issues"},"dist":{"shasum":"f330c86b7c2e40bc1cb5b147d0e38c7f54204527","tarball":"https://registry.npmjs.org/@avsbhq/next/-/next-1.0.1.tgz","fileCount":18,"integrity":"sha512-1aCKIa5LDSMBDr3QdCNTxRS+TzpESbOd4osJXdccVodpysvrfBhaGDLFyeygajjCCpGLQVYOxQU9crBglWNcMQ==","signatures":[{"sig":"MEYCIQDwA3dkE53JtJZwwWqTsx/0vqTLrZ0eQ3+/2cv+iq/lewIhAJtAFbt9ZjDZRJ6N6eLV7B9WnM0WERybjawjeYgl2v7C","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":54437},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./pages":{"types":"./dist/pages/getServerSideProps.d.ts","import":"./dist/pages/getServerSideProps.js","require":"./dist/pages/getServerSideProps.cjs"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js","require":"./dist/server.cjs"},"./middleware":{"types":"./dist/middleware.d.ts","import":"./dist/middleware.js","require":"./dist/middleware.cjs"}},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","test:watch":"vitest"},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"repository":{"url":"git+https://github.com/main1479/a-vs-b.git","type":"git","directory":"packages/avsb-next"},"_npmVersion":"10.9.8","description":"Next.js 15+ integration for the [A vs B](https://app.avsb.cloud) platform.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@avsbhq/core":"1.4.0","@avsbhq/node":"1.6.1","@avsbhq/react":"1.0.1","@avsbhq/utils":"1.0.2","@avsbhq/browser":"1.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.2.1","tsup":"^8.0.0","react":"^19.0.0","vitest":"^3.0.0","typescript":"^5.5.0","@types/react":"^19.0.0"},"peerDependencies":{"next":">=15.0.0","react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/next_1.0.1_1785346487770_0.7220533595380452","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@avsbhq/next","version":"1.0.2","_id":"@avsbhq/next@1.0.2","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/main1479/a-vs-b/tree/main/packages/avsb-next#readme","bugs":{"url":"https://github.com/main1479/a-vs-b/issues"},"dist":{"shasum":"7722cba28fb6ecb3b3cf560d60d7c86fb8b3ea01","tarball":"https://registry.npmjs.org/@avsbhq/next/-/next-1.0.2.tgz","fileCount":18,"integrity":"sha512-YXLo61I1G1BZqBaekU2H4GK8O3y6irF4kzhGx7U2PIexQrFKyJh6NTezR9Dmtopcp5yboLYT7e0NX27eYm0LQQ==","signatures":[{"sig":"MEYCIQD0u7MB0GmxjAgd36D2h4dkeOqfJkWutvH73lq28FqhUQIhAIdXBLiWv5liCTnhinAyCzJW/Mi7ErHhYbEYZR1Tzg6e","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":54437},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./pages":{"types":"./dist/pages/getServerSideProps.d.ts","import":"./dist/pages/getServerSideProps.js","require":"./dist/pages/getServerSideProps.cjs"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js","require":"./dist/server.cjs"},"./middleware":{"types":"./dist/middleware.d.ts","import":"./dist/middleware.js","require":"./dist/middleware.cjs"}},"scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","test:watch":"vitest"},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"repository":{"url":"git+https://github.com/main1479/a-vs-b.git","type":"git","directory":"packages/avsb-next"},"_npmVersion":"10.9.8","description":"Next.js 15+ integration for the [A vs B](https://app.avsb.cloud) platform.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"@avsbhq/core":"1.4.0","@avsbhq/node":"1.6.2","@avsbhq/react":"1.0.2","@avsbhq/utils":"1.0.2","@avsbhq/browser":"1.3.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.2.1","tsup":"^8.0.0","react":"^19.0.0","vitest":"^3.0.0","typescript":"^5.5.0","@types/react":"^19.0.0"},"peerDependencies":{"next":">=15.0.0","react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/next_1.0.2_1785347998811_0.7699203051504722","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@avsbhq/next","version":"1.1.0","keywords":["avsb","feature-flags","ab-testing","experiments","next","nextjs","react","sdk"],"license":"MIT","_id":"@avsbhq/next@1.1.0","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-next#readme","bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"dist":{"shasum":"72ecc6d35e8e255c9c650f950579d410d7b2ec1d","tarball":"https://registry.npmjs.org/@avsbhq/next/-/next-1.1.0.tgz","fileCount":21,"integrity":"sha512-G57sp0L4YOehrL1Hat/M/ORBx/CjFDf3yXa6hRODxAt1puO7NglNmN4zKXLWbpn4LzGsx3pI7vMKK6RZSrqqGQ==","signatures":[{"sig":"MEUCIQCOand4obcC1WoS++TdWuM6zaxiC/iIhN+iZInMW0DOuwIgXPPGLivjiGOo3gkJjySsvg1UzjJ4hRxJAlcHxjW8db0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":126814},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./pages":{"import":{"types":"./dist/pages/getServerSideProps.d.ts","default":"./dist/pages/getServerSideProps.js"},"require":{"types":"./dist/pages/getServerSideProps.d.cts","default":"./dist/pages/getServerSideProps.cjs"}},"./server":{"import":{"types":"./dist/server.d.ts","default":"./dist/server.js"},"require":{"types":"./dist/server.d.cts","default":"./dist/server.cjs"}},"./middleware":{"import":{"types":"./dist/middleware.d.ts","default":"./dist/middleware.js"},"require":{"types":"./dist/middleware.d.cts","default":"./dist/middleware.cjs"}},"./package.json":"./package.json"},"gitHead":"5eed46d5944b522ba0aa34e82850c98bb91667fe","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","test:watch":"vitest"},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"repository":{"url":"git+https://github.com/avsbhq/a-vs-b.git","type":"git","directory":"packages/avsb-next"},"_npmVersion":"10.9.8","description":"Next.js SDK for A vs B feature flags and experiments: server evaluation, safe hydration, middleware, and a one-line App Router root component.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"@avsbhq/core":"^1.4.0","@avsbhq/react":"^1.0.2","@avsbhq/utils":"^1.0.2","@avsbhq/browser":"^1.3.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.2.1","tsup":"^8.0.0","react":"^19.0.0","vitest":"^4.1.4","typescript":"^5.5.0","@types/react":"^19.0.0"},"peerDependencies":{"next":">=15.0.0","react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/next_1.1.0_1785971479524_0.7423440382457387","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@avsbhq/next","version":"1.1.1","description":"Next.js SDK for A vs B feature flags and experiments: server evaluation, safe hydration, middleware, and a one-line App Router root component.","keywords":["avsb","feature-flags","ab-testing","experiments","next","nextjs","react","sdk"],"license":"MIT","type":"module","sideEffects":false,"main":"dist/index.cjs","module":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./server":{"import":{"types":"./dist/server.d.ts","default":"./dist/server.js"},"require":{"types":"./dist/server.d.cts","default":"./dist/server.cjs"}},"./middleware":{"import":{"types":"./dist/middleware.d.ts","default":"./dist/middleware.js"},"require":{"types":"./dist/middleware.d.cts","default":"./dist/middleware.cjs"}},"./pages":{"import":{"types":"./dist/pages/getServerSideProps.d.ts","default":"./dist/pages/getServerSideProps.js"},"require":{"types":"./dist/pages/getServerSideProps.d.cts","default":"./dist/pages/getServerSideProps.cjs"}},"./package.json":"./package.json"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/avsbhq/a-vs-b.git","directory":"packages/avsb-next"},"homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-next#readme","bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest"},"peerDependencies":{"next":">=15.0.0","react":">=18.0.0"},"dependencies":{"@avsbhq/browser":"^1.3.1","@avsbhq/core":"^1.4.0","@avsbhq/react":"^1.0.2","@avsbhq/utils":"^1.0.2"},"devDependencies":{"@types/react":"^19.0.0","next":"^16.2.1","react":"^19.0.0","tsup":"^8.0.0","typescript":"^5.5.0","vitest":"^4.1.4"},"_id":"@avsbhq/next@1.1.1","gitHead":"d31d88fbf202f40041b4cb47f77882f454fa5ef8","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-QlWsURQr/PYDxz4/b3GHfMgQLFswrym+aHVatuJFgQFbAJGaezg8zC+Q+dHaZP7HLj8YqPoDStu8AaxZ+jKypw==","shasum":"a70e1cc01c6926fd7bc34d0e5008f5f8aaba2d4d","tarball":"https://registry.npmjs.org/@avsbhq/next/-/next-1.1.1.tgz","fileCount":21,"unpackedSize":126862,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEB7a8FhikD1rXBurzqR8RXmuPvSioSwMHK0HXegdx+5AiBv6kbvdHiygeUi7kfAhDn8K7Pe6tMKpTENM+SN+VYrkA=="}]},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"directories":{},"maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/next_1.1.1_1786795846760_0.22668186214696173"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-29T05:59:22.736Z","modified":"2026-08-15T12:10:47.101Z","1.0.0":"2026-05-29T05:59:23.019Z","1.0.1":"2026-07-29T17:34:47.931Z","1.0.2":"2026-07-29T17:59:58.971Z","1.1.0":"2026-08-05T23:11:19.709Z","1.1.1":"2026-08-15T12:10:46.905Z"},"bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"license":"MIT","homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-next#readme","keywords":["avsb","feature-flags","ab-testing","experiments","next","nextjs","react","sdk"],"repository":{"type":"git","url":"git+https://github.com/avsbhq/a-vs-b.git","directory":"packages/avsb-next"},"description":"Next.js SDK for A vs B feature flags and experiments: server evaluation, safe hydration, middleware, and a one-line App Router root component.","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"readme":"# @avsbhq/next\n\nNext.js integration for the [A vs B](https://app.avsb.cloud) platform.\n\nApp Router and Pages Router, with server evaluation, a safe bootstrap into the page, and one component that wires the whole thing up. Built on `@avsbhq/react`, whose hooks are all re-exported here, so app code needs one import.\n\n---\n\n## 1. Install\n\n```bash\nnpm install @avsbhq/next\n```\n\nNext.js 15 or later and React 18 or later, as peer dependencies.\n\nThat one install covers everything this package uses: `@avsbhq/core`, `@avsbhq/browser`, `@avsbhq/react` and `@avsbhq/utils` are real dependencies and arrive with it. `@avsbhq/node` is NOT one of them, so install it separately when you also want the server SDK.\n\n---\n\n## 2. Quickstart\n\nOne component, in your root layout:\n\n```tsx\n// app/layout.tsx\nimport { AvsbRoot } from '@avsbhq/next/server';\nimport type { ReactNode } from 'react';\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <body>\n        <AvsbRoot>{children}</AvsbRoot>\n      </body>\n    </html>\n  );\n}\n```\n\n```\n# .env\nAVSB_SDK_KEY=sdk_production_...\n```\n\nThen read flags in any client component:\n\n```tsx\n// app/CheckoutButton.tsx\n'use client';\nimport { useBoolFlag, useExposure, useTrack } from '@avsbhq/next';\nimport type { Flag } from '@avsbhq/next';\n\nexport function CheckoutButton() {\n  const flag: Flag<boolean> = useBoolFlag('new-checkout-flow', false);\n  const track = useTrack();\n  useExposure('new-checkout-flow');\n\n  return (\n    <button onClick={() => track('checkout_clicked')}>\n      {flag.isEnabled() ? 'New checkout' : 'Checkout'}\n    </button>\n  );\n}\n```\n\nThat is the whole setup. `<AvsbRoot>` resolves the visitor, fetches the datafile, evaluates every flag on the server, writes a safely escaped bootstrap into the page, and mounts the client provider with the datafile already in hand, so the HTML your users receive contains their variation and the browser makes no request before first paint.\n\nAdd the middleware to keep an anonymous visitor stable across visits:\n\n```ts\n// middleware.ts\nimport { withAvsb } from '@avsbhq/next/middleware';\n\nexport const middleware = withAvsb();\n\nexport const config = { matcher: ['/((?!_next|.*\\\\..*).*)'] };\n```\n\nWithout it, a Server Component cannot write a cookie, so an anonymous visitor is re-randomised on every request and their results do not add up. The SDK says so, once, in your server logs.\n\n---\n\n## 3. SDK keys\n\nOne key per environment, format `sdk_<environment>_<id>`, from **Settings**, then **Environments**, in your A vs B project.\n\n`<AvsbRoot>` reads `AVSB_SDK_KEY`, then `NEXT_PUBLIC_AVSB_SDK_KEY`, or takes `sdkKey` directly. You do not need both variables: the key reaches the browser as a prop either way, which is safe. An SDK key is scoped to one environment and grants flag reads only. It cannot write to your project or read another environment.\n\n---\n\n## 4. The whole type surface\n\nThe signatures below name the package's own types, plus React's and Next's. They come from here:\n\n```ts\nimport type {\n  AvsbBootstrapBlob,\n  EvalContext,\n  Flag,\n  FlagDatafile,\n  SerializedFlag,\n  SerializedFlagMap,\n} from '@avsbhq/next/server';\nimport type { AvsbClientOptions } from '@avsbhq/browser';\nimport type { RequestBoundClient } from '@avsbhq/utils';\nimport type { NextRequest } from 'next/server';\nimport type { ReactElement, ReactNode } from 'react';\n```\n\n### `@avsbhq/next/server`\n\n```ts\nfunction AvsbRoot(props: AvsbRootProps): Promise<ReactElement>;\n\ninterface AvsbRootProps {\n  sdkKey?: string; // defaults to AVSB_SDK_KEY, then NEXT_PUBLIC_AVSB_SDK_KEY\n  context?: EvalContext; // defaults to the anonymous visitor cookie\n  cdnHost?: string; // default 'https://cdn.avsb.cloud'\n  revalidate?: number; // seconds, default 60\n  cookie?: AnonCookieOptions;\n  emitBootstrapScript?: boolean; // default true\n  children: ReactNode;\n}\n\nfunction getDatafile(sdkKey: string, options?: GetDatafileOptions): Promise<FlagDatafile>;\n\ninterface GetDatafileOptions {\n  cdnHost?: string;\n  timeoutMs?: number; // default 5000\n  revalidate?: number; // seconds, default 60\n  signal?: AbortSignal;\n}\n\n/** The default `revalidate` window, in seconds. Mirrors the CDN cache header. */\nconst DEFAULT_REVALIDATE_SECONDS: number; // 60\n\nfunction evaluateFlagServer<T>(\n  datafile: FlagDatafile,\n  ctx: EvalContext,\n  flagKey: string,\n  defaultValue: T,\n): Flag<T>;\n\nfunction evaluateServerFlags(datafile: FlagDatafile, context: EvalContext): SerializedFlagMap;\n\n/** Strip one `Flag` down to the wire shape @avsbhq/react revives. */\nfunction serializeFlag(flag: Flag<unknown>): SerializedFlag;\n\nfunction AvsbHydrator(props: AvsbHydratorProps): Promise<ReactElement>;\n\ninterface AvsbHydratorProps {\n  datafile?: FlagDatafile;\n  sdkKey?: string;\n  cdnHost?: string;\n  context: EvalContext;\n  includeDatafile?: boolean; // default true\n}\n\nfunction serializeBootstrapBlob(blob: AvsbBootstrapBlob): string;\nconst AVSB_BOOTSTRAP_ID: string;\n\n// The anonymous visitor cookie, shared with the middleware.\nconst AVSB_ANON_COOKIE: string; // 'avsb_anon_id'\nconst AVSB_ANON_MAX_AGE_SECONDS: number; // one year\nfunction createAnonId(): string;\nfunction isAnonId(value: string | undefined | null): value is string;\nfunction resolveAnonId(existing: string | undefined | null): { id: string; isNew: boolean };\nfunction resolveAnonCookieOptions(options?: AnonCookieOptions): ResolvedAnonCookie;\n\ninterface AnonCookieOptions {\n  name?: string;\n  maxAgeSeconds?: number;\n  path?: string;\n  sameSite?: 'lax' | 'strict' | 'none';\n  secure?: boolean;\n  domain?: string;\n}\n\n/** The same attributes with every default filled in. */\ninterface ResolvedAnonCookie {\n  name: string;\n  maxAge: number;\n  path: string;\n  sameSite: 'lax' | 'strict' | 'none';\n  secure: boolean;\n  domain?: string;\n  /** Never httpOnly: the browser SDK reads this id too. */\n  httpOnly: false;\n}\n```\n\n### `@avsbhq/next/middleware`\n\n```ts\nfunction withAvsb(\n  middleware?: NextMiddleware,\n  options?: WithAvsbOptions,\n): (request: NextRequest) => Promise<Response>;\n\ntype NextMiddleware = (request: NextRequest) => MiddlewareResult | Promise<MiddlewareResult>;\ntype MiddlewareResult = Response | undefined | null | void;\n\ninterface WithAvsbOptions {\n  server?: NextAppAvsbServer;\n  contextFrom?: (request: NextRequest) => EvalContext | undefined;\n  withDecisionLog?: boolean;\n  cookie?: AnonCookieOptions | false;\n}\n\n/** Open the same request scope around a route handler. */\nfunction nextAppHandler<TArgs extends unknown[], TResult>(\n  server: NextAppAvsbServer,\n  opts: NextAppMiddlewareOptions,\n  handler: (req: Request, ...rest: TArgs) => Promise<TResult>,\n): (req: Request, ...rest: TArgs) => Promise<TResult>;\n\n/** The primitive both of the above are built on. Re-exported from @avsbhq/utils. */\nfunction withNextRequest<T>(\n  server: NextAppAvsbServer,\n  opts: NextAppMiddlewareOptions,\n  req: Request,\n  handler: () => Promise<T>,\n): Promise<T>;\n\ninterface NextAppAvsbServer {\n  forUser(ctx: EvalContext): RequestBoundClient;\n}\n\ninterface NextAppMiddlewareOptions {\n  contextFrom(req: Request): EvalContext | undefined;\n  withDecisionLog?: boolean;\n}\n```\n\n### `@avsbhq/next/pages`\n\n```ts\nfunction getServerSideAvsb<P extends Record<string, unknown> = Record<string, never>>(\n  options: GetServerSideAvsbOptions,\n  handler?: (avsbCtx: AvsbGsspContext) => Promise<{ props: P }>,\n): (gsspCtx: GsspContext) => Promise<GsspResult<P>>;\n\ninterface GetServerSideAvsbOptions {\n  sdkKey: string;\n  cdnHost?: string;\n  contextFrom?: (ctx: GsspContext) => EvalContext;\n  cookie?: AnonCookieOptions;\n}\n\n/** The slice of Next's `GetServerSidePropsContext` this helper needs. */\ninterface GsspContext {\n  req: {\n    headers: Record<string, string | string[] | undefined>;\n    cookies?: Record<string, string>;\n    url?: string;\n  };\n  res?: {\n    setHeader(name: string, value: string | string[]): void;\n    getHeader?(name: string): string | string[] | number | undefined;\n  };\n  params?: Record<string, string | string[]>;\n  query?: Record<string, string | string[]>;\n  [key: string]: unknown;\n}\n\n/** What the optional inner handler receives. */\ninterface AvsbGsspContext {\n  datafile: FlagDatafile;\n  /** Evaluated flags as `Flag` objects, methods included, for server logic. */\n  flags: Record<string, Flag>;\n  context: EvalContext;\n  gsspCtx: GsspContext;\n}\n\ninterface GsspResult<P extends Record<string, unknown>> {\n  props: P & { avsbBootstrap: AvsbPagesBootstrap };\n}\n\n/** The prop to spread into `<AvsbProvider>` in `_app.tsx`. */\ninterface AvsbPagesBootstrap {\n  sdkKey: string;\n  context: EvalContext;\n  bootstrap: FlagDatafile;\n  serverFlags: SerializedFlagMap;\n}\n```\n\n### `@avsbhq/next` (client)\n\n```ts\nfunction AvsbProvider(props: AvsbNextProviderProps): ReactElement;\n\ninterface AvsbNextProviderProps extends Omit<\n  AvsbClientOptions,\n  'sdkKey' | 'context' | 'bootstrap'\n> {\n  sdkKey: string;\n  context?: EvalContext;\n  bootstrap?: FlagDatafile;\n  serverFlags?: SerializedFlagMap;\n  children: ReactNode;\n}\n\nfunction readBootstrapBlob(): AvsbBootstrapBlob | null;\nfunction contextFromBlob(blob: AvsbBootstrapBlob | null): EvalContext | undefined;\n```\n\nPlus every hook from `@avsbhq/react`: `useFlag`, `useFlagValue`, `useBoolFlag`, `useStringFlag`, `useNumberFlag`, `useJsonFlag`, `useAllFlags`, `useFlagSuspense`, `useFlagChange`, `useFlagSubscription`, `useExposure`, `useAvsbStatus`, `useFlagReady`, `useAvsbClient`, `useAvsbClientSuspense`, `useIdentify`, `useAlias`, `useReset`, `useTrack`. Their signatures and the `Flag<T>` shape are documented in the `@avsbhq/react` README.\n\n### Typed flag keys\n\nEvery `flagKey` above is `string` until you generate your keys. Generate them\nand it becomes the union of this project's real flag keys, so a typo is a\ncompile error and your editor completes the list:\n\n```bash\nnpx avsb codegen --output src/generated/flags.ts\n```\n\nThe generated file declares your flags twice on purpose: an importable\n`FlagValues` interface for payload types, and a `declare global` block that\nteaches every hook, and `evaluateFlagServer`, which keys exist. Its important\nparts:\n\n```text\n// AUTO-GENERATED by @avsbhq/cli codegen. Do not edit by hand.\n\nexport interface FlagValues {\n  'checkout-v2': boolean\n  'hero-copy': 'control' | 'variant-a'\n  'theme': { primary: string }\n}\n\ndeclare global {\n  interface AvsbFlags {\n    'checkout-v2': boolean\n    'hero-copy': 'control' | 'variant-a'\n    'theme': { primary: string }\n  }\n}\n```\n\n```ts\nimport { useBoolFlag, useJsonFlag } from '@avsbhq/next';\nimport type { FlagValues } from './generated/flags';\n\nfunction useCheckoutFlags() {\n  const checkout = useBoolFlag('checkout-v2', false);\n\n  // A JSON flag types its payload from the generated table:\n  const theme = useJsonFlag<FlagValues['theme']>('theme', { primary: '#111' });\n\n  // Once the generated file exists, this line stops compiling:\n  // 'chekcout-v2' is not a flag key.\n  const typo = useBoolFlag('chekcout-v2', false);\n\n  return { checkout, theme };\n}\n```\n\nThe server side narrows the same way: the third argument of\n`evaluateFlagServer(datafile, ctx, flagKey, defaultValue)` is the same key type.\n\nNothing changes for a project that never runs `codegen`: with no generated file\nthe table is empty, every `flagKey` is exactly `string`, and every call you have\nalready written compiles unchanged. For a key computed at runtime, such as one\nread from a loop over the datafile, widen deliberately with\n`key as AvsbFlagKey` (that type is exported from `@avsbhq/core`).\n\n---\n\n## 5. Server Components\n\n`evaluateFlagServer` is a pure function: no network, no state, no side effects. Call it as often as you like inside one render.\n\n```tsx\n// app/pricing/page.tsx\nimport { getDatafile, evaluateFlagServer } from '@avsbhq/next/server';\nimport type { Flag } from '@avsbhq/next/server';\n\nexport default async function PricingPage() {\n  const datafile = await getDatafile(process.env.AVSB_SDK_KEY ?? '');\n  const context = { kind: 'user' as const, key: 'u_123', plan: 'pro' };\n  const flag: Flag<string> = evaluateFlagServer(datafile, context, 'pricing-experiment', 'control');\n\n  if (flag.variationKey === 'usage-based') return <UsageBasedPricing />;\n  return <DefaultPricing />;\n}\n```\n\n`getDatafile` is deduplicated within a request and cached across requests for `revalidate` seconds (60 by default), so ten server components asking for the same key cost one fetch.\n\n### Server evaluation and exposures\n\nServer evaluation fires no exposures, on purpose: an RSC render is not proof a person saw anything. Pair it with `useExposure` in the client component that renders the variation.\n\n```tsx\n// app/pricing/page.tsx (server)\nexport default async function PricingPage() {\n  const datafile = await getDatafile(process.env.AVSB_SDK_KEY ?? '');\n  const context = { kind: 'user' as const, key: 'u_123' };\n  const flag = evaluateFlagServer(datafile, context, 'pricing-experiment', 'control');\n\n  return <PricingPanel variation={flag.variationKey ?? 'control'} />;\n}\n```\n\n```tsx\n// app/PricingPanel.tsx (client)\n'use client';\nimport { useExposure } from '@avsbhq/next';\n\nexport function PricingPanel({ variation }: { variation: string }) {\n  useExposure('pricing-experiment');\n  return variation === 'usage-based' ? <UsageBased /> : <Tiered />;\n}\n```\n\nWithout that pairing, a server-rendered variant is invisible in your results.\n\n---\n\n## 6. Identity\n\n`<AvsbRoot>` uses the `avsb_anon_id` cookie for anonymous visitors, so the server render and the browser bucket the same person. Pass `context` once you know who they are:\n\n```tsx\nimport { AvsbRoot } from '@avsbhq/next/server';\nimport { auth } from './lib/auth';\n\nexport default async function RootLayout({ children }: { children: React.ReactNode }) {\n  const session = await auth();\n  const context = session\n    ? { kind: 'user' as const, key: session.userId, plan: session.plan }\n    : undefined;\n\n  return (\n    <html lang=\"en\">\n      <body>\n        <AvsbRoot context={context}>{children}</AvsbRoot>\n      </body>\n    </html>\n  );\n}\n```\n\nOmitting `context` keeps the anonymous cookie. Passing it makes the server and the client evaluate the same identified user.\n\n---\n\n## 7. Middleware\n\n```ts\n// middleware.ts\nimport { NextResponse } from 'next/server';\nimport type { NextRequest } from 'next/server';\nimport { withAvsb } from '@avsbhq/next/middleware';\nimport { server } from './lib/avsbServer';\n\nexport const middleware = withAvsb(\n  (request: NextRequest) => {\n    if (request.nextUrl.pathname === '/old') {\n      return NextResponse.redirect(new URL('/new', request.url));\n    }\n    return undefined;\n  },\n  {\n    server,\n    contextFrom: (request: NextRequest) => ({\n      kind: 'user',\n      key: request.cookies.get('uid')?.value ?? 'anon',\n    }),\n  },\n);\n\nexport const config = { matcher: ['/((?!_next|.*\\\\..*).*)'] };\n```\n\nWhat it guarantees:\n\n- Your middleware's return value passes through untouched. Return nothing and the request continues, exactly like `NextResponse.next()`.\n- The anonymous visitor cookie is maintained on whatever response goes out, redirects included.\n- With `server` and `contextFrom`, the request runs inside an evaluation scope, so `getRequestClient()` from `@avsbhq/utils` works downstream.\n\nOne honest limit: that scope covers the middleware chain, route handlers, and server actions. It does **not** reach React Server Component renders, which Next.js runs separately. For RSCs, evaluate with `evaluateFlagServer` or let `<AvsbRoot>` do it.\n\nFor route handlers there is also `nextAppHandler(server, options, handler)`, which opens the same scope per request.\n\n---\n\n## 8. Pages Router\n\n```tsx\n// pages/checkout.tsx\nimport { getServerSideAvsb } from '@avsbhq/next/pages';\n\nexport const getServerSideProps = getServerSideAvsb({\n  sdkKey: process.env.AVSB_SDK_KEY ?? '',\n});\n\nexport default function CheckoutPage() {\n  return <CheckoutContent />;\n}\n```\n\n```tsx\n// pages/_app.tsx\nimport { AvsbProvider } from '@avsbhq/next';\nimport type { AppProps } from 'next/app';\n\nexport default function App({ Component, pageProps }: AppProps) {\n  const { avsbBootstrap, ...rest } = pageProps;\n  return (\n    <AvsbProvider {...avsbBootstrap}>\n      <Component {...rest} />\n    </AvsbProvider>\n  );\n}\n```\n\n`props.avsbBootstrap` is exactly the provider's props: the key, the context that was evaluated, the datafile, and the evaluated values. An inner handler still gets `Flag` objects for server-side branching:\n\n```ts\nexport const getServerSideProps = getServerSideAvsb(\n  { sdkKey: process.env.AVSB_SDK_KEY ?? '' },\n  async ({ flags }) => ({\n    props: { showBanner: flags['show-banner']?.isEnabled() ?? false },\n  }),\n);\n```\n\n---\n\n## 9. The bootstrap blob\n\n`<AvsbRoot>` writes one, and you rarely need to think about it. Render `<AvsbHydrator>` yourself only when your provider lives outside the `AvsbRoot` subtree:\n\n```tsx\nimport { AvsbHydrator } from '@avsbhq/next/server';\n\ndeclare const context: EvalContext;\n\n<AvsbHydrator sdkKey={process.env.AVSB_SDK_KEY ?? ''} context={context} />;\n```\n\nIt emits a `<script type=\"application/json\" id=\"__AVSB_BOOTSTRAP__\">` element carrying the evaluated flags, the full evaluation context, and (by default) the datafile itself. `<AvsbProvider>` reads it on mount when it was not given props.\n\nThe blob is escaped: every `<` becomes a JSON escape, so a flag value containing a script-closing payload cannot end the data block early. Variation values are authored in your dashboard, so this matters: before the escape existed, any org member could put a script on every page of an app using the hydrator.\n\nOne thing the blob cannot do is fix the SERVER render: a script tag in the DOM does not exist while the server is rendering. That is what `serverFlags` (a prop) is for, and why `<AvsbRoot>` passes them.\n\n---\n\n## 10. Error handling\n\n```tsx\n'use client';\nimport { useAvsbStatus } from '@avsbhq/next';\n\nexport function FlagStatus() {\n  const { status, error, degraded } = useAvsbStatus();\n  if (status === 'error') return <ErrorBanner message={error?.message} />;\n  if (degraded) return <StaleFlagsNotice />;\n  return null;\n}\n```\n\nOn the server, a failed datafile fetch does not fail the page. `<AvsbRoot>` logs the reason (status, URL, and the fix), renders anyway, and every read returns the default you passed until the client's own retry succeeds. `getDatafile` throws if you call it yourself, so wrap it if you want the same behaviour by hand.\n\n---\n\n## 11. Tracking\n\n```tsx\n'use client';\nimport { useTrack } from '@avsbhq/next';\n\nconst track = useTrack();\ntrack('purchase_completed', { value: 199.0, properties: { plan: 'annual' } });\n```\n\nServer-side, use `@avsbhq/node`. It is a separate install (`npm install @avsbhq/node`), not a dependency of this package:\n\n```ts\nimport { AvsbServer } from '@avsbhq/node';\n\nexport const server = new AvsbServer({ sdkKey: process.env.AVSB_SDK_KEY ?? '' });\n```\n\n---\n\n## 12. Testing\n\n```tsx\nimport { render, screen } from '@testing-library/react';\nimport { AvsbTestProvider } from '@avsbhq/react/testing';\nimport { CheckoutButton } from './CheckoutButton';\n\ntest('renders the new checkout when the flag is on', () => {\n  render(\n    <AvsbTestProvider flags={{ 'new-checkout-flow': true }}>\n      <CheckoutButton />\n    </AvsbTestProvider>,\n  );\n  expect(screen.getByRole('button')).toHaveTextContent('New checkout');\n});\n```\n\nServer code is tested by calling it:\n\n```ts\nimport { evaluateFlagServer } from '@avsbhq/next/server';\nimport { createTestDatafile } from '@avsbhq/react/testing';\n\nconst datafile = createTestDatafile({ 'my-flag': true });\nconst flag = evaluateFlagServer(datafile, { kind: 'user', key: 'u_1' }, 'my-flag', false);\nexpect(flag.value).toBe(true);\n```\n\n---\n\n## 13. Breaking changes in this release\n\n| Change                                                                                                          | What to do                                                                                                                 |\n| --------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |\n| `avsbNextAppMiddleware` is deleted. It returned an empty 200 for every matched request, which blanked the site. | Use `withAvsb(yourMiddleware?, options?)`. It wraps your middleware and continues the request when there is nothing to do. |\n| `AvsbServerProvider` is deleted.                                                                                | Use `<AvsbRoot>`, which also mounts the client provider.                                                                   |\n| The bootstrap blob carries the datafile by default (`includeDatafile: true`).                                   | Nothing, unless you were relying on the client fetching it again.                                                          |\n| The blob is escaped, and carries the full evaluation context.                                                   | Nothing. Client-side evaluation now sees the attributes the server used.                                                   |\n| `getDatafile` requests `{cdnHost}/{sdkKey}/datafile.json`.                                                      | Nothing. The old path did not exist and always failed.                                                                     |\n| `getServerSideAvsb` returns `props.avsbBootstrap` (an object), not `props.__avsbBootstrap` (a string).          | Spread it into `<AvsbProvider>` in `_app.tsx`.                                                                             |\n| `<AvsbProvider>` requires `sdkKey`.                                                                             | Pass it. The \"no sdkKey needed\" pattern never worked: without a key the client cannot refresh the datafile.                |\n| The client entry is built with a `'use client'` banner.                                                         | You no longer need your own wrapper file to import the provider.                                                           |\n\n---\n\n## 14. Migration\n\n### From LaunchDarkly Next.js\n\n| LaunchDarkly Next.js           | `@avsbhq/next`                                       |\n| ------------------------------ | ---------------------------------------------------- |\n| `withLDProvider(options)(App)` | `<AvsbRoot>` in `app/layout.tsx`                     |\n| `getLDBootstrapData`           | `<AvsbRoot>`, or `getDatafile` plus `<AvsbHydrator>` |\n| `useLDClient()`                | `useAvsbClient()`                                    |\n| `useFlags()`                   | `useAllFlags()`                                      |\n| `useLDFlag('key', default)`    | `useFlag('key', default).value`                      |\n\n### From Statsig Next.js\n\n| Statsig Next.js      | `@avsbhq/next`                                                      |\n| -------------------- | ------------------------------------------------------------------- |\n| `StatsigProvider`    | `<AvsbRoot>`                                                        |\n| `useGate('gate')`    | `useBoolFlag('gate', false).isEnabled()` plus `useExposure('gate')` |\n| `useStatsigClient()` | `useAvsbClient()`                                                   |\n| `prefetchStatsig`    | `<AvsbRoot>`, or `getDatafile` on the server                        |\n\n---\n\n## 15. Set this up with your AI assistant\n\nPaste this into Claude Code, Cursor, or any coding assistant:\n\n```text\nSet up A vs B feature flags in this Next.js project.\n\n1. Run: npx @avsbhq/cli init\n   Use my saved CLI login: do not ask me for a token and do not put one in any file.\n   It detects Next.js, writes the SDK key into .env.local as AVSB_SDK_KEY, and\n   writes an example file.\n2. Install @avsbhq/next with this project's package manager.\n3. Wrap the tree once in app/layout.tsx with <AvsbRoot> from @avsbhq/next/server,\n   then read the flag the example names from a client component.\n4. Every getter returns a Flag object, so read .value or call .isEnabled(), and\n   always pass a fallback. Reading a flag records nothing: call useExposure(key)\n   where the variation is shown.\n5. Then run: npx @avsbhq/cli codegen\n\nDone looks like: the app starts, the flag reads without throwing, and avsb init\nprints the line confirming it saw the first check-in.\n```\n\n`avsb init` ends by waiting for your app's first check-in and printing what it\nsaw, so the terminal tells you it works rather than the dashboard.\n","readmeFilename":"README.md"}