Experiments
Run A/B experiments with deterministic, SSR-safe user assignment and percentage-based bucketing.
Defining Experiments
const config = defineAccess({
// ...
experiments: {
"checkout-flow": {
active: true,
variants: ["control", "optimized", "minimal"],
defaultVariant: "control",
allocation: { control: 50, optimized: 30, minimal: 20 },
},
"pricing-page": {
active: true,
variants: ["current", "new-layout"],
defaultVariant: "current",
// Without allocation: even 50/50 split
},
"old-experiment": {
active: false,
variants: ["a", "b"],
defaultVariant: "a",
// Inactive: all users get defaultVariant
},
},
});Experiment Definition
| Field | Type | Description |
|---|---|---|
active | boolean | Whether the experiment is running |
variants | string[] | List of variant names |
defaultVariant | string | Fallback variant when inactive |
allocation | Record<string, number> | Percentage per variant (must sum to 100) |
Assignment Mechanism
- Uses a deterministic hash of
userId + experimentId - SSR-safe — produces identical results on server and client
- Without
allocation: variants are distributed evenly - With
allocation: percentage-based bucketing
User hash: 0────────50────────80──────100
│ control │ optimized│ minimal│When an experiment is inactive, all users receive the defaultVariant with active: false.
Using in Components
<Experiment> Component
import { Experiment } from "react-access-engine";
<Experiment
id="checkout-flow"
variants={{
control: <ClassicCheckout />,
optimized: <OptimizedCheckout />,
minimal: <MinimalCheckout />,
}}
fallback={<ClassicCheckout />}
/>;The component renders the variant assigned to the current user. If the experiment is inactive or not found, it renders the fallback.
useExperiment Hook
import { useExperiment } from "react-access-engine";
function CheckoutPage() {
const { experimentId, variant, active } = useExperiment("checkout-flow");
// Track exposure
useEffect(() => {
if (active) {
analytics.track("experiment_exposure", { experimentId, variant });
}
}, [experimentId, variant, active]);
switch (variant) {
case "optimized":
return <OptimizedCheckout />;
case "minimal":
return <MinimalCheckout />;
default:
return <ClassicCheckout />;
}
}Tracking with Plugins
Use the analytics plugin to automatically track experiment assignments:
import { createAnalyticsPlugin } from "react-access-engine";
const analyticsPlugin = createAnalyticsPlugin({
adapter: {
track: (event, properties) => {
// Send to your analytics provider
mixpanel.track(event, properties);
},
},
trackExperiments: true,
});
const config = defineAccess({
// ...
plugins: [analyticsPlugin],
experiments: {
/* ... */
},
});Events emitted:
rac:experiment_assign— when a user is assigned to a variant
Even vs Custom Allocation
Even Distribution (default)
experiments: {
'test-1': {
active: true,
variants: ['a', 'b', 'c'],
defaultVariant: 'a',
// Each variant gets ~33.3% of traffic
},
}Custom Allocation
experiments: {
'test-2': {
active: true,
variants: ['control', 'treatment'],
defaultVariant: 'control',
allocation: {
control: 90, // 90% see control
treatment: 10, // 10% see treatment
},
},
}The allocation values must sum to 100.
Deactivating Experiments
Set active: false to gracefully deactivate. All users will receive the defaultVariant:
experiments: {
'finished-experiment': {
active: false,
variants: ['control', 'winner'],
defaultVariant: 'winner', // Winner gets rolled out to 100%
},
}