/Docs

Next.js 15 Pages Router integration

This guide assumes a Next.js 15 project using the Pages Router (pages/directory). By the end you'll have server-side flag evaluation in getServerSideProps with the datafile hydrated into the client provider so flags render correctly on the first paint — no loading flash — using @avsbhq/next and @avsbhq/react.

1

Install

bash
1npm install @avsbhq/next@^1 @avsbhq/react@^1 @avsbhq/browser@^1
2

Obtain your SDK key

Open Settings > Environments in your A vs B project and copy the SDK key for the target environment. Add it to your environment file:

.env.local
bash
1AVSB_SDK_KEY=sdk_production_xxxxxxxxxxxxxxxx
Info
Server SDK keys are server-only. Do not prefix them with NEXT_PUBLIC_. The Pages Router only exposes NEXT_PUBLIC_ variables to the browser.
3

Wrap _app with the provider

Create a client-side AvsbProvider in pages/_app.tsx. Pass the bootstrap prop that getServerSideProps will hydrate into page props. The provider stays mounted across client navigations.

pages/_app.tsx
tsx
1import type { AppProps } from 'next/app'
2import { AvsbProvider } from '@avsbhq/react'
3import type { FlagDatafile } from '@avsbhq/core'
4
5interface AvsbPageProps {
6 avsbBootstrap?: FlagDatafile
7}
8
9export default function App({ Component, pageProps }: AppProps<AvsbPageProps>) {
10 return (
11 <AvsbProvider
12 sdkKey={process.env.NEXT_PUBLIC_AVSB_SDK_KEY!}
13 bootstrap={pageProps.avsbBootstrap}
14 >
15 <Component {...pageProps} />
16 </AvsbProvider>
17 )
18}
Info
In Pages Router the provider needs a sdkKey in the browser for polling updates. Expose a public key via NEXT_PUBLIC_AVSB_SDK_KEY (safe to expose — it is a read-only evaluation key, not a write key).
4

Fetch bootstrap data in getServerSideProps

Use getDatafile from @avsbhq/next/server to pre-fetch the datafile on the server for every page that needs flags without a loading state. Pass the serialised datafile as avsbBootstrap in props.

pages/dashboard.tsx
tsx
1import type { GetServerSideProps } from 'next'
2import { getDatafile } from '@avsbhq/next/server'
3import type { FlagDatafile } from '@avsbhq/core'
4
5interface Props {
6 avsbBootstrap: FlagDatafile
7}
8
9export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
10 const datafile = await getDatafile(process.env.AVSB_SDK_KEY!)
11 return { props: { avsbBootstrap: datafile } }
12}
13
14export default function DashboardPage() {
15 // Flags are ready synchronously — no loading flash.
16 return <Dashboard />
17}
5

Read a flag

components/HeroSection.tsx
tsx
1import { useFlag } from '@avsbhq/react'
2
3export function HeroSection() {
4 const heroVariant = useFlag('homepage-hero', 'control')
5
6 if (heroVariant.value === 'variant-a') return <HeroVariantA />
7 if (heroVariant.value === 'variant-b') return <HeroVariantB />
8 return <HeroControl />
9}
6

Track an event

tsx
1import { useTrack } from '@avsbhq/react'
2
3export function CheckoutButton() {
4 const track = useTrack()
5
6 return (
7 <button
8 type="button"
9 onClick={() => track('checkout_started', { value: 99.0 })}
10 >
11 Proceed to checkout
12 </button>
13 )
14}
7

Identify a user

Call useIdentify after the user logs in to replace the anonymous context with an authenticated one.

tsx
1import { useIdentify } from '@avsbhq/react'
2
3export function PostLoginCallback({ userId }: { userId: string }) {
4 const identify = useIdentify()
5 identify({ kind: 'user', key: userId })
6 return null
7}

SSR and hydration

Unlike the App Router, the Pages Router runs getServerSideProps separately from the React tree. The integration pattern is:

  1. Fetch the datafile in getServerSideProps using getDatafile.
  2. Pass it as avsbBootstrap in the serialised page props.
  3. _app.tsx forwards it to AvsbProvider via the bootstrap prop.
  4. The provider evaluates flags synchronously from the bootstrap on the first render, matching the server-rendered HTML.

For pages that do not call getServerSideProps, the provider fetches the datafile asynchronously on mount. A brief loading state may appear. Wrap critical flag-gated UI in a Suspense boundary or use useFlagReady to show a skeleton until the client is initialised.

tsx
1import { useFlagReady } from '@avsbhq/react'
2
3export function FlagGatedSection() {
4 const ready = useFlagReady()
5 if (!ready) return <Skeleton />
6
7 return <ActualContent />
8}

Graceful shutdown

AvsbProvider calls client.close() on unmount. For custom Node servers, handle SIGTERM:

ts
1process.on('SIGTERM', async () => {
2 await server.close()
3 process.exit(0)
4})

Testing

tsx
1import { AvsbTestProvider, TestData } from '@avsbhq/test'
2import { render } from '@testing-library/react'
3import { HeroSection } from './HeroSection'
4
5const td = TestData.flag('homepage-hero').stringFlag().fallthroughVariation('variant-a')
6
7render(
8 <AvsbTestProvider flags={[td.build()]}>
9 <HeroSection />
10 </AvsbTestProvider>
11)

What's next