Feature Flags Setup
Feature flags allow deploying code without releasing features: enable features for specific users, audience segments, or traffic percentages without new deployments.
Why Feature Flags
- Trunk-based development — everyone works in main/master, unfinished features hidden behind flags
- Canary releases — first 1% users, then 10%, then everyone
- A/B testing — different versions for different segments
- Kill switch — instant disable of broken feature without deployment
- Beta programs — enable for specific accounts
Self-hosted: Unleash
# Docker Compose
cat > docker-compose.yml << 'EOF'
services:
db:
image: postgres:16
environment:
POSTGRES_DB: unleash
POSTGRES_USER: unleash
POSTGRES_PASSWORD: secret
volumes:
- pg_data:/var/lib/postgresql/data
unleash:
image: unleashorg/unleash-server:latest
ports:
- "4242:4242"
environment:
DATABASE_URL: postgresql://unleash:secret@db/unleash
INIT_CLIENT_API_TOKENS: "default:development.unleash-token"
depends_on:
- db
volumes:
pg_data:
EOF
docker compose up -d
Integration in Next.js:
// lib/unleash.ts
import { initialize, isEnabled, getVariant } from 'unleash-client';
export const unleash = initialize({
url: 'http://unleash:4242/api',
appName: 'my-nextjs-app',
customHeaders: { Authorization: process.env.UNLEASH_TOKEN! },
});
// Usage in Server Component
export async function isFeatureEnabled(flag: string, userId?: string) {
await unleash.isReady();
return isEnabled(flag, { userId });
}
// app/page.tsx (Server Component)
import { isFeatureEnabled } from '@/lib/unleash';
export default async function HomePage() {
const showNewHero = await isFeatureEnabled('new-hero-section', userId);
return showNewHero ? <NewHeroSection /> : <OldHeroSection />;
}
Cloud: Growthbook (Open Source SaaS)
# Self-hosted
docker run -d \
-e MONGODB_URI=mongodb://mongo/growthbook \
-e APP_ORIGIN=http://localhost:3000 \
-p 3000:3000 \
growthbook/growthbook:latest
// SDK for React
import { GrowthBook, GrowthBookProvider, useFeatureValue } from '@growthbook/growthbook-react';
const gb = new GrowthBook({
apiHost: 'https://cdn.growthbook.io',
clientKey: process.env.NEXT_PUBLIC_GROWTHBOOK_CLIENT_KEY,
enableDevMode: process.env.NODE_ENV !== 'production',
trackingCallback: (experiment, result) => {
// Send to analytics
gtag('event', 'experiment_viewed', {
experiment_id: experiment.key,
variant_id: result.key,
});
},
});
// Provider
function App({ Component, pageProps }) {
return (
<GrowthBookProvider growthbook={gb}>
<Component {...pageProps} />
</GrowthBookProvider>
);
}
// Usage in component
function PricingPage() {
const showAnnualPricing = useFeatureValue('annual-pricing', false);
const ctaText = useFeatureValue('cta-text', 'Get Started');
return (
<div>
<button>{ctaText}</button>
{showAnnualPricing && <AnnualPricingSection />}
</div>
);
}
Flags with Targeting (PostHog)
// PostHog: feature flags + analytics in one tool
import PostHog from 'posthog-node';
const client = new PostHog(process.env.POSTHOG_API_KEY!, {
host: 'https://app.posthog.com'
});
// Targeting by user properties
const isEnabled = await client.isFeatureEnabled(
'new-checkout-flow',
userId,
{
// PostHog will check against flag rules
personProperties: {
plan: user.plan, // 'pro' | 'enterprise'
country: user.country,
created_at: user.createdAt,
}
}
);
Usage Patterns
Gradual rollout:
0% → deploy code behind flag
1% → enable for 1% users, monitor errors
10% → expand, monitor metrics
50% → half rollout, final check
100% → full rollout
→ remove flag from code (zombie flags — technical debt)
Flag lifecycle:
// Flags should have lifespan
// Create with expiration date:
// "new-onboarding-flow" → expires: 2024-03-01
// After this date — delete flag from code
Self-hosted Unleash or Growthbook setup with application integration — 1–2 working days.







