Next.js (Pages Router)

@avsbhq/next ships a getServerSideProps wrapper for the Pages Router. It resolves the visitor, fetches the datafile, evaluates every flag, and hands _app.tsx one prop that is exactly the provider's props. The server-rendered HTML and the first client render agree, so there is no flash of default values on a page that uses it.

This page assumes Next.js 15 or later and React 18 or later. On the App Router, read Next.js (App Router) instead.

1

Install

One package. @avsbhq/core, @avsbhq/browser, @avsbhq/react and @avsbhq/utils arrive with it.

2

Add your SDK key

Open Environments in your A vs B project sidebar and copy the key for the environment you are wiring up.

3

Wrap getServerSideProps

getServerSideAvsb does the fetch, the anonymous visitor cookie, and the evaluation, then adds avsbBootstrap to your props.

4

Spread avsbBootstrap into the provider

In pages/_app.tsx. It carries the key, the context, the datafile, and the evaluated values.

5

Read flags and record exposures

Hooks come from @avsbhq/next. Reads never fire exposures; useExposure does.

Install

Shell
npm install @avsbhq/next
Shell1 line

Add your SDK key

Shell
# .env.localAVSB_SDK_KEY=sdk_production_ttqm0eaj4vth1krcb2xn
Shell2 lines

One server-side variable is enough. getServerSideAvsb reads it on the server and passes the key to the browser inside avsbBootstrap, so you do not need a NEXT_PUBLIC_ copy.

Your SDK key is public
Your SDK key is a public identifier, not a secret: it is safe to ship in browser and mobile bundles, it can only fetch that environment's flag configuration and send events, and it can never read or change anything in your dashboard. Credentials covers all four A vs B credentials and which one to reach for.

Wrap getServerSideProps

TypeScript React
// pages/checkout.tsximport { getServerSideAvsb } from '@avsbhq/next/pages'export const getServerSideProps = getServerSideAvsb({  sdkKey: process.env.AVSB_SDK_KEY ?? '',})export default function CheckoutPage() {  return <CheckoutContent />}
TypeScript React10 lines

The options, in full:

TypeScript
import type { GsspContext } from '@avsbhq/next/pages'import type { AnonCookieOptions } from '@avsbhq/next/server'import type { EvalContext } from '@avsbhq/next'interface GetServerSideAvsbOptions {  /** The environment SDK key. */  sdkKey: string  /** CDN base URL. Default 'https://cdn.avsb.cloud'. */  cdnHost?: string  /** Maps the request to an evaluation context. Omit it for the anonymous cookie. */  contextFrom?: (ctx: GsspContext) => EvalContext  /** Anonymous cookie name, lifetime, and attributes, when `contextFrom` is omitted. */  cookie?: AnonCookieOptions}
TypeScript14 lines

GsspContext is the slice of Next's GetServerSidePropsContext this helper needs: req.headers, req.cookies, req.url, res.setHeader, plus params and query. Your real getServerSideProps context satisfies it.

Need server-side branching as well? Pass an inner handler. It receives the datafile, the context, and the evaluated flags as real Flag objects, and its props are merged with avsbBootstrap:

TypeScript
// pages/pricing.tsximport { getServerSideAvsb } from '@avsbhq/next/pages'export const getServerSideProps = getServerSideAvsb(  { sdkKey: process.env.AVSB_SDK_KEY ?? '' },  async ({ flags }) => ({    props: { showBanner: flags['show-banner']?.isEnabled() ?? false },  }),)
TypeScript9 lines

Spread avsbBootstrap into the provider

TypeScript React
// pages/_app.tsximport { AvsbProvider } from '@avsbhq/next'import type { AppProps } from 'next/app'export default function App({ Component, pageProps }: AppProps) {  const { avsbBootstrap, ...rest } = pageProps  return (    <AvsbProvider {...avsbBootstrap}>      <Component {...rest} />    </AvsbProvider>  )}
TypeScript React12 lines

avsbBootstrap is exactly the provider's props:

TypeScript
import type { EvalContext, FlagDatafile, SerializedFlagMap } from '@avsbhq/next'interface AvsbPagesBootstrap {  sdkKey: string  /** The context the server evaluated. */  context: EvalContext  /** The datafile, so the browser makes no request before first paint. */  bootstrap: FlagDatafile  /** The evaluated values, so the hydration render matches the HTML. */  serverFlags: SerializedFlagMap}
TypeScript11 lines
Info

The provider stays mounted across client navigations, and pages that do not use getServerSideAvsb still work: the client fetches the datafile itself and reads answer once it lands. Only the first paint of those pages can show defaults.

Read a flag

TypeScript React
// components/HeroSection.tsximport { useStringFlag, useExposure } from '@avsbhq/next'import type { Flag } from '@avsbhq/next'export function HeroSection() {  const hero: Flag<string> = useStringFlag('homepage_hero', 'control')  useExposure('homepage_hero')  if (hero.value === 'variant-a') return <HeroVariantA />  if (hero.value === 'variant-b') return <HeroVariantB />  return <HeroControl />}
TypeScript React12 lines

Reads return a Flag<T> object, never a bare value, and always take a default: it is what you get before the SDK is ready, when the key does not exist, and when the flag's declared type does not match the getter. useExposure is what records that the visitor saw the variation, once per flag per mounted view.

Track a conversion

TypeScript React
// components/CheckoutButton.tsximport { useTrack } from '@avsbhq/next'export function CheckoutButton() {  const track = useTrack()  return (    <button type="button" onClick={() => track('checkout_started', { value: 99.0 })}>      Proceed to checkout    </button>  )}
TypeScript React12 lines

Identify a visitor

Call identify from an event handler or an effect, never during render: it re-buckets every flag, and a render must not do that.

TypeScript React
// components/PostLoginCallback.tsximport { useEffect } from 'react'import { useIdentify } from '@avsbhq/next'export function PostLoginCallback({ userId, plan }: { userId: string; plan: string }) {  const identify = useIdentify()  useEffect(() => {    identify({ kind: 'user', key: userId, plan })  }, [identify, userId, plan])  return null}
TypeScript React13 lines

Better still, hand the identity to contextFrom in getServerSideAvsb so the server evaluates the signed-in visitor in the first place.

Waiting for flags on a page without server props

TypeScript React
// components/FlagGatedSection.tsximport { useFlagReady } from '@avsbhq/next'export function FlagGatedSection() {  const ready = useFlagReady()  if (!ready) return <Skeleton />  return <ActualContent />}
TypeScript React8 lines

useFlagReady() means flags are readable now. useAvsbStatus() adds status and degraded when you want to tell "still loading" apart from "serving a stale datafile after a failed refresh".

Testing

@avsbhq/react/testing renders the real provider around a real client built from a flags map, so a component test exercises the same code path production does:

TypeScript React
// components/HeroSection.test.tsximport { render, screen } from '@testing-library/react'import { AvsbTestProvider } from '@avsbhq/react/testing'import { HeroSection } from './HeroSection'test('renders variant A when the flag says so', () => {  render(    <AvsbTestProvider flags={{ 'homepage_hero': 'variant-a' }}>      <HeroSection />    </AvsbTestProvider>,  )  expect(screen.getByRole('heading')).toHaveTextContent('Variant A')})
TypeScript React14 lines

What changed in 1.x

ChangeWhat to do
getServerSideAvsb returns props.avsbBootstrap (an object), not props.__avsbBootstrap (a string).Spread it into AvsbProvider in _app.tsx.
AvsbProvider requires sdkKey.Spread avsbBootstrap, which carries it.
Reads no longer fire an exposure per render.Add useExposure(flagKey) where the variation is shown, or exposure counts drop to zero.
The client entry is built with a 'use client' banner.You no longer need a wrapper file to import the provider.

What's next

Was this helpful?