npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@avsbhq/next

v1.0.0

Published

Next.js 15+ integration for the [A vs B](https://app.avsb.cloud) platform.

Downloads

176

Readme

@avsbhq/next

Next.js 15+ integration for the A vs B platform.

Full App Router support with React Server Components (RSC), server actions, and the middleware layer. Pages Router is supported via a separate subpath. Built on @avsbhq/react — all React hooks are re-exported so you only need this package, not @avsbhq/react separately.


1. Install

npm install @avsbhq/next

Next.js 15 or later and React 18 or later are required as peer dependencies. Node.js 18 or later.


2. Quickstart

App Router

Step 1 — Server component fetches datafile and injects bootstrap:

// app/layout.tsx — a React Server Component
import { getDatafile, AvsbHydrator } from '@avsbhq/next/server'
import { Providers } from './Providers'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const ctx = { kind: 'user' as const, key: cookies().get('uid')?.value ?? 'anon' }
  const datafile = await getDatafile(process.env.AVSB_SDK_KEY!, { cdnHost: 'https://cdn.avsb.cloud' })

  return (
    <html>
      <body>
        <AvsbHydrator datafile={datafile} context={ctx} />
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

Step 2 — Client provider reads from the injected bootstrap:

// app/Providers.tsx
'use client'
import { AvsbProvider } from '@avsbhq/next'

export function Providers({ children }: { children: React.ReactNode }) {
  // No sdkKey needed — reads from window.__AVSB_BOOTSTRAP__ written by AvsbHydrator
  return <AvsbProvider>{children}</AvsbProvider>
}

Step 3 — Use flags anywhere:

// app/CheckoutButton.tsx
'use client'
import { useBoolFlag, useTrack } from '@avsbhq/next'

export function CheckoutButton() {
  const flag = useBoolFlag('new-checkout-flow', false)
  const track = useTrack()

  return (
    <button onClick={() => track('checkout_clicked')}>
      {flag.isEnabled() ? 'New checkout' : 'Checkout'}
    </button>
  )
}

Server component evaluation (no client round-trip):

// app/pricing/page.tsx — RSC
import { getDatafile, evaluateFlagServer } from '@avsbhq/next/server'

export default async function PricingPage() {
  const ctx = { kind: 'user' as const, key: getCurrentUserId() }
  const datafile = await getDatafile(process.env.AVSB_SDK_KEY!)
  const flag = evaluateFlagServer(datafile, ctx, 'pricing-experiment', 'control')

  if (flag.variationKey === 'usage-based') return <UsageBasedPricing />
  return <DefaultPricing />
}

3. SDK keys

@avsbhq/next uses both server and client keys depending on context:

| Context | Key type | Variable | |---|---|---| | getDatafile, AvsbHydrator, evaluateFlagServer | Server key (sdk-server-...) | AVSB_SDK_KEY | | <AvsbProvider> client-side | Client key (sdk-client-...) | NEXT_PUBLIC_AVSB_SDK_KEY |

When using the AvsbHydrator pattern (recommended), the client-side provider bootstraps from the injected datafile and never fetches independently. You only need AVSB_SDK_KEY (server).

For independent client-side fetching (no SSR bootstrap):

<AvsbProvider sdkKey={process.env.NEXT_PUBLIC_AVSB_SDK_KEY!} context={ctx}>

4. Identity

All @avsbhq/react identity hooks are re-exported directly:

import { useIdentify, useAlias, useReset } from '@avsbhq/next'

For server-side identity in RSCs, pass the context directly to evaluateFlagServer or AvsbHydrator:

const ctx = {
  kind: 'user' as const,
  key: session.userId,
  plan: session.plan,
}

const flag = evaluateFlagServer(datafile, ctx, 'new-feature', false)

5. Multi-context

// Server component
const ctx = {
  kind: 'multi' as const,
  user: { kind: 'user' as const, key: userId, plan: 'pro' },
  organization: { kind: 'organization' as const, key: orgId, tier: 'enterprise' },
}
const datafile = await getDatafile(process.env.AVSB_SDK_KEY!)
const flag = evaluateFlagServer(datafile, ctx, 'enterprise-dashboard', false)
// Client component
const identify = useIdentify()
identify({
  kind: 'multi',
  user: { kind: 'user', key: userId, plan: 'pro' },
  organization: { kind: 'organization', key: orgId, tier: 'enterprise' },
})

6. Reading flags

In RSCs (server-side, no client state)

import { getDatafile, evaluateFlagServer } from '@avsbhq/next/server'

const datafile = await getDatafile(process.env.AVSB_SDK_KEY!)
const flag = evaluateFlagServer(datafile, ctx, 'my-flag', false)
// flag is Flag<boolean> — same shape as client-side hooks

evaluateFlagServer is a pure function — no network, no state, no side effects. Call it as many times as needed within a single render.

In client components

All @avsbhq/react hooks are available:

import { useBoolFlag, useStringFlag, useNumberFlag, useJsonFlag, useFlag, useFlagValue, useAllFlags } from '@avsbhq/next'

See @avsbhq/react documentation for full hook descriptions.


7. Tracking events

'use client'
import { useTrack } from '@avsbhq/next'

const track = useTrack()
track('purchase_completed', { value: 199.0, properties: { plan: 'annual' } })

Server-side tracking via @avsbhq/node:

// app/_lib/avsbServer.ts
import { AvsbServer } from '@avsbhq/node'
export const server = new AvsbServer({ sdkKey: process.env.AVSB_SDK_KEY! })
await server.onReady()

// In a server action or route handler:
server.forUser(ctx).track('form_submitted', { value: 1 })

8. Error handling

Client-side

import { useAvsbStatus } from '@avsbhq/next'

const { status, error } = useAvsbStatus()
if (status === 'error') return <ErrorBanner />

Server-side

getDatafile throws on network failure. Wrap in try/catch and fall back to a pre-cached datafile or serve defaults:

let datafile: FlagDatafile
try {
  datafile = await getDatafile(process.env.AVSB_SDK_KEY!)
} catch {
  datafile = fallbackDatafile  // stale cached datafile
}

9. SSR / hydration

The AvsbHydrator server component writes the evaluated datafile and context to window.__AVSB_BOOTSTRAP__ via an inline <script type="application/json"> tag. <AvsbProvider> on the client reads this blob on mount and resolves onReady() immediately with source: 'bootstrap' — no network request on the client.

This ensures the same flag values are used on the server render and the initial client render, preventing React hydration mismatches.

// Full pattern:
<AvsbHydrator datafile={datafile} context={ctx} />
<AvsbProvider>{children}</AvsbProvider>
// The provider reads window.__AVSB_BOOTSTRAP__ automatically

If you need to render a component tree on the server with the correct flag values AND hydrate on the client, use AvsbServerProvider:

import { AvsbServerProvider } from '@avsbhq/next/server'

<AvsbServerProvider datafile={datafile} context={ctx}>
  <MyServerTree />
  <AvsbHydrator datafile={datafile} context={ctx} />
</AvsbServerProvider>

10. Graceful shutdown

Client-side: <AvsbProvider> flushes queued events on unmount automatically. For root-level providers that never unmount, use:

if (typeof window !== 'undefined') {
  window.addEventListener('beforeunload', () => { void client?.flush() })
}

Server-side (@avsbhq/node instance):

process.on('SIGTERM', async () => {
  await server.close()
  process.exit(0)
})

11. Testing

import { render, screen } from '@testing-library/react'
import { AvsbTestProvider } from '@avsbhq/test'
import { CheckoutButton } from './CheckoutButton'

test('renders new checkout when flag on', () => {
  render(
    <AvsbTestProvider mockFlags={{ 'new-checkout-flow': true }}>
      <CheckoutButton />
    </AvsbTestProvider>
  )
  expect(screen.getByRole('button')).toHaveTextContent('New checkout')
})

For RSC testing, call evaluateFlagServer directly in your test with a mock datafile:

import { evaluateFlagServer } from '@avsbhq/next/server'
import { createTestDatafile } from '@avsbhq/test'

const datafile = createTestDatafile({ 'my-flag': true })
const flag = evaluateFlagServer(datafile, { kind: 'user', key: 'u_1' }, 'my-flag', false)
expect(flag.value).toBe(true)

12. App Router middleware

// middleware.ts (project root)
import { avsbNextAppMiddleware } from '@avsbhq/next/middleware'
import { server } from './lib/avsbServer'

export const middleware = avsbNextAppMiddleware({
  server,
  contextFrom: (req) => ({
    kind: 'user' as const,
    key: req.cookies.get('uid')?.value ?? 'anon',
  }),
})

export const config = { matcher: '/(.*)' }

The middleware opens an AsyncLocalStorage scope so you can call getRequestClient() from @avsbhq/utils anywhere in the request chain without passing the client explicitly.


13. Pages Router

// pages/checkout.tsx
import { getServerSideAvsb } from '@avsbhq/next/pages'
import { server } from '../lib/avsbServer'

export const getServerSideProps = getServerSideAvsb({
  server,
  contextFrom: (ctx) => ({
    kind: 'user' as const,
    key: ctx.req.cookies.uid ?? 'anon',
  }),
})

export default function CheckoutPage({ avsbBootstrap }) {
  return (
    <AvsbProvider bootstrap={avsbBootstrap}>
      <CheckoutContent />
    </AvsbProvider>
  )
}

14. Migration

From LaunchDarkly Next.js

| LaunchDarkly Next.js | @avsbhq/next | |---|---| | withLDProvider(options)(App) | <AvsbProvider> in app/Providers.tsx | | getLDBootstrapData | getDatafile + AvsbHydrator | | useLDClient() | useAvsbClient() | | useFlags() | useAllFlags() | | useLDFlag('key', default) | useFlag('key', default).value | | LDProvider + createInspectors | <AvsbProvider onError={...} logger={...}> |

Key differences:

  • The AvsbHydrator server component eliminates the need for separate SSR bootstrap wiring — inject it once in your root layout.
  • Client components only need import ... from '@avsbhq/next' — no separate @avsbhq/react install.
  • evaluateFlagServer is a pure function usable in any RSC without provider context.

From Statsig Next.js

| Statsig Next.js | @avsbhq/next | |---|---| | StatsigProvider | <AvsbProvider> | | useGate('gate') | useBoolFlag('gate', false).isEnabled() | | useStatsigClient() | useAvsbClient() | | prefetchStatsig | getDatafile (server) + AvsbHydrator |