Feature Variants & A/B Testing
Feature variants allow you to create A/B/n tests and gradual rollouts with consistent user assignment.
Basic Variant Configuration
ts
// feature-flags.config.ts
import { defineFeatureFlags } from '#feature-flags/handler'
export default defineFeatureFlags(() => {
return {
// Simple A/B test
buttonColor: {
enabled: true,
value: 'blue', // default value
variants: [
{ name: 'blue', weight: 50 },
{ name: 'red', weight: 50, value: 'red' }
]
},
// A/B/C/D test
homepage: {
enabled: true,
variants: [
{ name: 'original', weight: 40, value: 'v1' },
{ name: 'redesign', weight: 30, value: 'v2' },
{ name: 'minimal', weight: 20, value: 'v3' },
{ name: 'experimental', weight: 10, value: 'v4' }
]
},
// Gradual rollout (20% get new feature)
newFeature: {
enabled: true,
variants: [
{ name: 'disabled', weight: 80, value: false },
{ name: 'enabled', weight: 20, value: true }
]
}
}
})How Variant Assignment Works
Persistent Assignment
Users receive the same variant consistently across sessions and devices. This persistence is crucial for accurate A/B testing because it ensures:
- Consistent Experience: Users don't see different variants on each visit
- Reliable Analytics: User behavior can be accurately tracked to a specific variant
- Fair Testing: Each user contributes to only one variant's metrics
Identifier Priority
To ensure persistent assignment, the module needs a stable identifier for each user. Identifiers are selected using this priority order:
- User ID (
context.user.idorcontext.userId) - Highest priority - Session ID (from cookies:
session-idornuxt-session) - IP Address (from request headers)
- Fallback:
'anonymous'(if none of the above are available)
Best practice: Populate context.user.id in your middleware for the most consistent variant assignment:
ts
// server/middleware/user-context.ts
export default defineEventHandler((event) => {
const user = await getUserFromSession(event)
if (user) {
event.context.user = {
id: user.id, // This will be used for variant assignment
role: user.role,
// ... other user properties
}
}
})Using Variants in Templates
vue
<template>
<!-- Different button colors based on variant -->
<button
v-feature="'buttonColor:blue'"
class="bg-blue-500 text-white px-4 py-2"
>
Blue Button (50% of users)
</button>
<button
v-feature="'buttonColor:red'"
class="bg-red-500 text-white px-4 py-2"
>
Red Button (50% of users)
</button>
<!-- Conditional content based on variant -->
<div v-if="getVariant('homepage') === 'redesign'">
<h1>Welcome to our new design!</h1>
</div>
</template>Programmatic Variant Checking
ts
const { isEnabled, getVariant, getValue } = useFeatureFlags()
// Check if user is in specific variant
if (isEnabled('buttonColor:red')) {
// User sees red button
}
// Get the assigned variant name
const variant = getVariant('buttonColor') // 'blue' | 'red'
// Get the variant value
const color = getValue('buttonColor') // 'blue' | 'red'
// Use in computed properties
const buttonClass = computed(() => {
const color = getValue('buttonColor')
return `bg-${color}-500 text-white px-4 py-2`
})Common Patterns
Gradual Rollout
Gradually increase the percentage of users who see a new feature:
ts
// Week 1: 10% of users
newCheckout: {
enabled: true,
variants: [
{ name: 'old', weight: 90, value: false },
{ name: 'new', weight: 10, value: true }
]
}
// Week 2: Increase to 25%
// { name: 'old', weight: 75, value: false },
// { name: 'new', weight: 25, value: true }
// Week 3: Increase to 50%
// { name: 'old', weight: 50, value: false },
// { name: 'new', weight: 50, value: true }A/B Test
Compare two versions to determine which performs better:
ts
ctaButton: {
enabled: true,
variants: [
{ name: 'control', weight: 50, value: { color: 'blue', text: 'Sign Up' } },
{ name: 'treatment', weight: 50, value: { color: 'red', text: 'Get Started' } }
]
}Canary Release
Release new features to a small subset of users first:
ts
newApiIntegration: {
enabled: true,
variants: [
{ name: 'old-api', weight: 95, value: { endpoint: '/api/v1', version: 1 } },
{ name: 'new-api', weight: 5, value: { endpoint: '/api/v2', version: 2 } }
]
}