@badgerlytics/sdk
v1.0.2
Published
Badgerlytics SDK — SSR experiment assignment and React helpers for the tracking script.
Maintainers
Readme
@badgerlytics/sdk
Package for users of Badgerlytics, the AI-Powered Analytics and A/B Testing Platform. More info →. COMING SOON!
Official SDK — server-side experiment assignment (no flash of wrong content) and React helpers around the browser tracking script.
Install
npm install @badgerlytics/sdkInstall the peer dependency for the integration you use:
| Import path | Peer dependency |
|-------------|-----------------|
| @badgerlytics/sdk/nextjs | next >= 13.4 |
| @badgerlytics/sdk/react | react >= 18 |
| @badgerlytics/sdk/remix | react-router >= 7 or @remix-run/node >= 2 |
| @badgerlytics/sdk/nuxt | nuxt >= 3 |
| @badgerlytics/sdk/astro | astro >= 4 |
You also need the tracking script on your pages (see React below). SSR middleware and the script share the same _bai_visitor / _bai_flags / _bai_traits cookies.
TypeScript
Type definitions ship with the package — no extra @types install and no typescript dependency required in your app.
import { createBadgerlyticsMiddleware, getVariation } from '@badgerlytics/sdk/nextjs';
import { useVariation } from '@badgerlytics/sdk/react';Plain JavaScript projects are unchanged; bundlers and Node resolve the same .js entry points.
Pre-release builds
To try a build before it lands on latest:
npm install @badgerlytics/sdk@testMiddleware parity
All four framework entry points share the same assignment engine (runAssignment). Each export is listed below.
Shared exports (every middleware package)
| Export | Description |
|--------|-------------|
| createBadgerlyticsMiddleware(options) | Framework middleware factory |
| getVariation(source, flagKey, fallback?) | Variation from forwarded x-bai-* headers |
| getAssignments(source) | Full { flagKey: { i, v } } map from headers |
| getVisitorId(source) | Visitor id from x-bai-visitor |
| getTraits(source) | Trait bag from x-bai-traits |
| isFlagEnabled(source, flagKey) | true when assigned to v1 (control is off) |
| isVariation(source, flagKey, expected) | Compare variation key |
| setTraitsOnResponse(response, traits) | Set/clear _bai_traits cookie (see traits) |
| runBadgerlyticsAssignment(request, options) | Manual assignment for one Request |
| readVisitorFromRequest / readTraitsFromRequest / readFlagsFromRequest | Read cookies from a Web Request |
| writeAssignmentCookiesOnResponse(response, opts) | Append visitor + flags Set-Cookie on a Web Response |
| buildTraitsSetCookieHeader(traits, opts) | Low-level Set-Cookie string |
| Cookie/header constants | VISITOR_COOKIE, FLAGS_COOKIE, TRAITS_COOKIE, x-bai-* headers |
Framework-specific helpers
| Package | Read assignments in SSR | Set traits on login |
|---------|-------------------------|---------------------|
| nextjs | getVariation(req \| headers(), …) | setTraitsOnResponse(NextResponse \| Response \| pages/api res, …) |
| remix | getVariation(request, …) after middleware forwards headers | setTraitsOnResponse(action Response, …) |
| nuxt | getVariationFromEvent(event, …) from event.context.badgerlytics | setTraitsOnEvent(event, …) |
| astro | getVariationFromLocals(Astro.locals, …) | setTraitsOnResponse(route Handler Response, …) |
| Package | Manual assignment without global middleware |
|---------|---------------------------------------------|
| nextjs / remix / astro | runBadgerlyticsAssignment(request, options) |
| nuxt | runBadgerlyticsAssignmentForEvent(event, options) |
nextjs-only: writeAssignmentCookies(NextResponse, opts) — sets visitor + flags via NextResponse.cookies (used by the built-in middleware).
astro / nuxt locals & event helpers: getAssignmentsFromLocals, getTraitsFromLocals, getVisitorIdFromLocals, isFlagEnabledFromLocals, and the Nuxt mirrors getAssignmentsFromEvent, getTraitsFromEvent, setTraitsOnEvent, etc.
Fundamental incompatibilities
Where SSR reads assignments
- Next.js and Remix forward
x-bai-assignments(and related headers) on the request. UsegetVariation(req)orgetVariation(request). - Nuxt stores assignment on
event.context.badgerlytics(middleware also patchesevent.node.req.headers). PrefergetVariationFromEvent(event)in server routes. - Astro does not replace the incoming
Request; assignments live onAstro.locals.badgerlytics. UsegetVariationFromLocals(Astro.locals)— notgetVariation(Astro.request).
- Next.js and Remix forward
Setting traits
setTraitsOnResponseworks on WebResponse, Next.jsNextResponse, and legacypages/apires(setHeader).- Nuxt server routes should use
setTraitsOnEvent(event, traits).
React (
@badgerlytics/sdk/react) is client-only:useSetTraits()callswindow.badgerlytics.setTraits(), which re-evaluates enrollment immediately — flag hooks (useVariation,useIsFlagEnabled) re-render in place once the returned promise resolves. Server-rendered output only picks the traits up on the next request unless you also set_bai_traitson the server.Edge vs Node: Middleware runs wherever your framework runs it (Next Edge, Nitro, etc.). The property CDN embed must be reachable from that runtime.
Next.js
Server-side assignment via middleware, then read variants in pages or server components.
1. Middleware
Create middleware.js at your project root:
import { createBadgerlyticsMiddleware } from '@badgerlytics/sdk/nextjs';
export const middleware = createBadgerlyticsMiddleware({
propertyId: 'your-property-id',
});
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};2. Read assignments
Pages router (getServerSideProps):
import { getVariation } from '@badgerlytics/sdk/nextjs';
export async function getServerSideProps({ req }) {
return {
props: {
heroVariation: getVariation(req, 'hero_test', 'control'),
},
};
}App router (server components):
import { headers } from 'next/headers';
import { getVariation } from '@badgerlytics/sdk/nextjs';
export default async function Page() {
const h = await headers();
const variation = getVariation(h, 'hero_test', 'control');
return variation === 'v1' ? <HeroB /> : <HeroA />;
}3. Audience traits on the server
import { setTraitsOnResponse } from '@badgerlytics/sdk/nextjs';
// App Router Route Handler
export async function POST(request) {
const user = await authenticate(request);
const response = Response.json({ ok: true });
setTraitsOnResponse(response, { signed_in: true, plan: user.plan });
return response;
}
// Legacy pages/api — Node `res` is supported
export default function handler(req, res) {
setTraitsOnResponse(res, { signed_in: true });
res.status(200).json({ ok: true });
}4. Tracking script
<script>window.badgerlyticsConfig = { propertyId: 'your-property-id' };</script>
<script src="https://cdn.badgerlytics.com/scripts/badgerlytics.min.js" defer></script>React
Client-side hooks and context around window.badgerlytics. Pass config to BadgerlyticsProvider to set window.badgerlyticsConfig and inject the tracking script — no separate embed component required.
import { BadgerlyticsProvider, useVariation, useIsFlagEnabled, useSetTraits } from '@badgerlytics/sdk/react';
export default function App() {
return (
<BadgerlyticsProvider config={{ propertyId: 'your-property-id' }}>
<Home />
</BadgerlyticsProvider>
);
}| Prop | Description |
|------|-------------|
| config | badgerlyticsConfig (at minimum propertyId). When set, the script loads automatically. |
| scriptSrc | Optional full URL to badgerlytics.min.js (default: production CDN). |
| autoLoad | Default true when config is passed. Set false if the script is already in HTML. |
| Export | Description |
|--------|-------------|
| BadgerlyticsProvider | Loads script (when config is set), waits for init, exposes context |
| ensureTrackingScript({ config, scriptSrc }) | Imperative load without React |
| useVariation(flagKey, fallback?) | Current variation after script loads |
| useIsFlagEnabled(flagKey) | true when assigned to v1 |
| useSetTraits() | Calls badgerlytics.setTraits(...) in the browser |
If you already load the script via <script> tags, omit config or use autoLoad={false} and wrap with <BadgerlyticsProvider autoLoad={false}>.
Hooks wait for the tracker automatically (via callWhenTrackerReady), so you never need to poll for window.badgerlytics yourself. The tracking script also buffers tracking calls (e.g. trackConversion) made while its configuration is still loading and sends them as one batch once it's ready — calls are only lost if the script tag itself hasn't executed yet, which the provider's injection ordering prevents.
Remix / React Router
import { createBadgerlyticsMiddleware, getVariation, setTraitsOnResponse } from '@badgerlytics/sdk/remix';
export const middleware = [
createBadgerlyticsMiddleware({ propertyId: 'your-property-id' }),
];In loaders:
export async function loader({ request }) {
return { hero: getVariation(request, 'hero_test', 'control') };
}In a login action:
export async function action({ request }) {
const user = await login(request);
const response = redirect('/app');
setTraitsOnResponse(response, { signed_in: true, plan: user.plan });
return response;
}Nuxt
server/middleware/badgerlytics.ts:
import { createBadgerlyticsMiddleware } from '@badgerlytics/sdk/nuxt';
export default createBadgerlyticsMiddleware({
propertyId: 'your-property-id',
});In server routes:
import { getVariationFromEvent, setTraitsOnEvent } from '@badgerlytics/sdk/nuxt';
export default defineEventHandler((event) => {
const hero = getVariationFromEvent(event, 'hero_test', 'control');
return { hero };
});
// Login handler
export default defineEventHandler(async (event) => {
const user = await login(event);
setTraitsOnEvent(event, { signed_in: true, plan: user.plan });
return { ok: true };
});Astro
src/middleware.js:
import { defineMiddleware } from 'astro:middleware';
import { createBadgerlyticsMiddleware } from '@badgerlytics/sdk/astro';
export const onRequest = defineMiddleware(
createBadgerlyticsMiddleware({ propertyId: 'your-property-id' })
);In pages (use locals, not Astro.request headers):
---
import { getVariationFromLocals, setTraitsOnResponse } from '@badgerlytics/sdk/astro';
const hero = getVariationFromLocals(Astro.locals, 'hero_test', 'control');
---
{hero === 'v1' ? <HeroB /> : <HeroA />}For Astro API routes that return a Response, setTraitsOnResponse works the same as Remix.
How SSR assignment works
Browser ──▶ Your framework middleware (@badgerlytics/sdk)
│ read cookies → fetch CDN embed → bucket visitor
│ Set-Cookie + forward x-bai-* headers (or locals / event.context)
▼
SSR reads getVariation() / getVariationFromLocals() / getVariationFromEvent()
▼
HTML + cookies ──▶ Browser tracking scriptBucketing matches the tracking script exactly for the same (visitorId, flagKey, iteration).
