Client-side SDKs for cuped.io — A/B testing with CUPED variance reduction.
This repo is a pnpm workspace with three packages:
| Package | npm | Source | Role |
|---|---|---|---|
@cuped-io/flame |
npmjs.com | ./packages/flame | Core SDK. IIFE for <script> tag use; ESM/CJS for npm consumers. |
@cuped-io/flame-react |
npmjs.com | ./packages/flame-react | React bindings: <CupedProvider>, useExperiment, <Experiment>, useObserve. |
@cuped-io/flame-edge |
npmjs.com | ./packages/flame-edge | Edge resolver + signed cookie utilities for zero-flash SSR. Web Crypto only. |
- Fetches active experiments for a project
- Assigns visitors to variants
- Applies variant changes to the DOM (8 change types:
text,html,attribute,class,style,css,visibility,redirect) - Auto-detects e-commerce events (add to cart, checkout, etc.)
- Tracks observations with experiment assignments for server-side goal matching
You'll need a DSN. Sign up at cuped.io, create a project, and copy the DSN from Settings → Install snippet (it looks like https://YOUR_KEY@api.cuped.io).
Drop this in your <head> and define variants in the dashboard:
<script
src="https://cdn.cuped.io/flame.js"
data-dsn="https://YOUR_KEY@api.cuped.io"
></script>pnpm add @cuped-io/flame @cuped-io/flame-reactimport { CupedProvider, useExperiment } from '@cuped-io/flame-react';
function App() {
return (
<CupedProvider dsn="https://YOUR_KEY@api.cuped.io">
<Hero />
</CupedProvider>
);
}
function Hero() {
const { variant } = useExperiment('hero-cta');
return <button>{variant?.name === 'treatment' ? 'Buy now' : 'Get started'}</button>;
}The recommended setup for Next.js. Variants are resolved at the edge before the first byte renders, so server HTML matches the assigned variant from request #1.
pnpm add @cuped-io/flame @cuped-io/flame-react @cuped-io/flame-edge.env.local:
CUPED_DSN=https://YOUR_KEY@api.cuped.io
NEXT_PUBLIC_CUPED_DSN=https://YOUR_KEY@api.cuped.io
CUPED_COOKIE_SECRET=<generate with: openssl rand -base64 32>
middleware.ts:
import { createCupedMiddleware } from '@cuped-io/flame-edge/next';
export default createCupedMiddleware({
dsn: process.env.CUPED_DSN!,
secret: process.env.CUPED_COOKIE_SECRET!,
});
export const config = {
matcher: ['/((?!_next/static|_next/image|api/|favicon.ico).*)'],
};app/providers.tsx:
'use client';
import { CupedProvider } from '@cuped-io/flame-react';
import type { PrehydratedState } from '@cuped-io/flame';
export function Providers({
children,
prehydrated,
}: {
children: React.ReactNode;
prehydrated?: PrehydratedState;
}) {
return (
<CupedProvider
dsn={process.env.NEXT_PUBLIC_CUPED_DSN!}
prehydrated={prehydrated}
>
{children}
</CupedProvider>
);
}app/layout.tsx:
import { cookies } from 'next/headers';
import { readPrehydratedForServerComponent } from '@cuped-io/flame-edge/next';
import { Providers } from './providers';
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const prehydrated = await readPrehydratedForServerComponent(
await cookies(),
process.env.CUPED_COOKIE_SECRET!,
);
return (
<html>
<body>
<Providers prehydrated={prehydrated ?? undefined}>{children}</Providers>
</body>
</html>
);
}That's it — useExperiment and <Experiment> work the same way as Option 2.
examples/script-tag— Static HTML page exercising all 8 change types.examples/next-app— Next.js App Router app with edge middleware + signed prehydrated cookie. Includes verification steps.
- React + Next.js guide — full reference including all hooks, the
<Experiment>component, and zero-flash SSR - Script-tag SDK
- REST API
- Methodology: CUPED & Bayesian
pnpm install
pnpm test # all packages
pnpm typecheck
pnpm lint
pnpm build # IIFE + ESM/CJS across packagesReleases are managed by changesets and published to npm via OIDC trusted publishing with provenance attestations.
MIT — see LICENSE.