LaunchDarkly
"LaunchDarkly: feature flags, targeting rules, segments, experiments, metrics, Node/React SDK, bootstrap, streaming"
LaunchDarkly is a feature management platform that decouples deployment from release. Every feature ships behind a flag, giving teams precise control over who sees what, when. The platform emphasizes real-time flag evaluation via streaming connections, rich targeting with user segments, and built-in experimentation to measure the impact of features before full rollout. Flags are not just booleans — they are multivariate controls that can return strings, numbers, JSON objects, enabling dynamic configuration at scale. ## Key Points 1. **Use multi-contexts** to model real-world relationships (user, organization, device). This enables targeting at every level without duplicating attributes. 2. **Bootstrap flags on the server** for SSR applications. This eliminates the flash of default content while the client SDK initializes its streaming connection. 3. **Keep flag lifecycles short.** Create a flag, roll it out, confirm it works, then remove the flag and dead code path. Stale flags accumulate tech debt. 4. **Use variation detail** (`variationDetail`) during debugging and experiment analysis to understand _why_ a particular value was served. 5. **Track metrics close to the user action.** Call `track()` immediately when the event occurs rather than batching in application code — the SDK handles batching internally. 6. **Namespace flag keys** by domain: `checkout-express-enabled`, `search-v2-algorithm`, `billing-retry-limit`. This scales across teams. 7. **Set meaningful defaults.** The fallback value in `variation()` is used when the SDK cannot reach LaunchDarkly. Defaults should always be the safe, existing behavior. 1. **Evaluating flags in tight loops.** Flag evaluation is fast but not free. Cache the result in a local variable when evaluating inside loops or repeated renders. 2. **Using flags as a database.** Flags are for feature control, not persistent user preferences. Store user settings in your own data layer. 3. **Anonymous contexts without stable keys.** If you generate a new random key on every request, percentage rollouts become random rather than sticky. Use cookies or device IDs for consistency. 4. **Ignoring flag prerequisites.** If flag B depends on flag A being on, model this as a prerequisite in LaunchDarkly rather than nesting conditionals in code — the platform enforces the dependency. 5. **Never cleaning up flags.** Every flag that ships successfully should have a removal ticket. Use LaunchDarkly's code references and stale flag detection to find flags safe to remove.
skilldb get feature-flags-services-skills/LaunchDarklyFull skill: 321 linesLaunchDarkly
Core Philosophy
LaunchDarkly is a feature management platform that decouples deployment from release. Every feature ships behind a flag, giving teams precise control over who sees what, when. The platform emphasizes real-time flag evaluation via streaming connections, rich targeting with user segments, and built-in experimentation to measure the impact of features before full rollout. Flags are not just booleans — they are multivariate controls that can return strings, numbers, JSON objects, enabling dynamic configuration at scale.
Setup
Node.js Server SDK
import * as LaunchDarkly from "@launchdarkly/node-server-sdk";
const client = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY!);
await client.waitForInitialization({ timeout: 5 });
// Evaluate a boolean flag
const context: LaunchDarkly.LDContext = {
kind: "user",
key: "user-123",
email: "alice@example.com",
custom: {
plan: "enterprise",
region: "us-east-1",
},
};
const showNewDashboard = await client.variation(
"new-dashboard",
context,
false // default value
);
if (showNewDashboard) {
renderNewDashboard();
} else {
renderLegacyDashboard();
}
React SDK with Bootstrap
// app/providers.tsx
import { LDProvider } from "launchdarkly-react-client-sdk";
interface Props {
children: React.ReactNode;
bootstrapFlags: Record<string, unknown>;
userKey: string;
}
export function FeatureFlagProvider({
children,
bootstrapFlags,
userKey,
}: Props) {
return (
<LDProvider
clientSideID={process.env.NEXT_PUBLIC_LD_CLIENT_ID!}
context={{
kind: "user",
key: userKey,
anonymous: false,
}}
options={{
bootstrap: bootstrapFlags,
streaming: true,
sendEventsOnlyForVariation: true,
}}
>
{children}
</LDProvider>
);
}
Server-Side Bootstrap for SSR
// app/layout.tsx — Next.js App Router
import * as LaunchDarkly from "@launchdarkly/node-server-sdk";
const ldServer = LaunchDarkly.init(process.env.LD_SDK_KEY!);
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
await ldServer.waitForInitialization({ timeout: 5 });
const context: LaunchDarkly.LDContext = {
kind: "user",
key: "anonymous",
anonymous: true,
};
const allFlags = await ldServer.allFlagsState(context);
const bootstrapFlags = allFlags.toJSON();
return (
<html>
<body>
<FeatureFlagProvider
bootstrapFlags={bootstrapFlags}
userKey="anonymous"
>
{children}
</FeatureFlagProvider>
</body>
</html>
);
}
Key Techniques
Multi-Context Targeting
// Target by user AND organization simultaneously
const multiContext: LaunchDarkly.LDContext = {
kind: "multi",
user: {
key: "user-456",
email: "bob@acme.com",
custom: { role: "admin" },
},
organization: {
key: "org-acme",
name: "Acme Corp",
custom: { plan: "enterprise", employeeCount: 500 },
},
device: {
key: "device-abc",
custom: { os: "ios", appVersion: "3.2.1" },
},
};
const limit = await client.variation("api-rate-limit", multiContext, 1000);
Multivariate Flags and JSON Variations
// Flag returning a JSON config object
interface CheckoutConfig {
maxRetries: number;
timeoutMs: number;
provider: "stripe" | "adyen" | "braintree";
showExpressCheckout: boolean;
}
const checkoutConfig = await client.jsonVariation<CheckoutConfig>(
"checkout-config",
context,
{
maxRetries: 3,
timeoutMs: 5000,
provider: "stripe",
showExpressCheckout: false,
}
);
const paymentClient = createPaymentClient({
provider: checkoutConfig.provider,
timeout: checkoutConfig.timeoutMs,
retries: checkoutConfig.maxRetries,
});
Listening for Real-Time Flag Changes
// React hook for live updates
import { useFlags, useLDClient } from "launchdarkly-react-client-sdk";
import { useEffect } from "react";
function FeatureAwareComponent() {
const flags = useFlags();
const ldClient = useLDClient();
useEffect(() => {
const handler = (newValue: boolean, oldValue: boolean) => {
console.log(`Flag changed: ${oldValue} -> ${newValue}`);
// Optionally trigger analytics or UI transitions
};
ldClient?.on("change:new-checkout-flow", handler);
return () => {
ldClient?.off("change:new-checkout-flow", handler);
};
}, [ldClient]);
if (flags["new-checkout-flow"]) {
return <NewCheckout />;
}
return <LegacyCheckout />;
}
Experimentation and Metrics
// Track custom metric events for experiments
const ldClient = useLDClient();
function handlePurchaseComplete(orderId: string, revenue: number) {
// Numeric metric for revenue tracking
ldClient?.track("purchase-completed", undefined, revenue);
// Conversion metric
ldClient?.track("checkout-conversion");
// Metric with additional data
ldClient?.track("order-placed", {
orderId,
itemCount: cart.items.length,
paymentMethod: selectedMethod,
});
}
// Server-side experiment tracking
async function processOrder(userId: string, order: Order) {
const context: LaunchDarkly.LDContext = {
kind: "user",
key: userId,
};
// Evaluate which variation the user is in
const detail = await client.variationDetail(
"pricing-experiment",
context,
"control"
);
// detail.variationIndex tells you which bucket
// detail.reason explains why (e.g., "RULE_MATCH", "FALLTHROUGH")
client.track("order-value", context, null, order.total);
client.track("conversion", context);
}
Segments and Progressive Rollouts
// Middleware for percentage-based rollout with sticky assignment
import { NextRequest, NextResponse } from "next/server";
export async function middleware(req: NextRequest) {
await ldServer.waitForInitialization({ timeout: 5 });
const userId = req.cookies.get("uid")?.value ?? "anonymous";
const context: LaunchDarkly.LDContext = {
kind: "user",
key: userId,
custom: {
country: req.geo?.country ?? "unknown",
pathname: req.nextUrl.pathname,
},
};
const detail = await ldServer.variationDetail(
"new-landing-page",
context,
false
);
if (detail.value && req.nextUrl.pathname === "/") {
const url = req.nextUrl.clone();
url.pathname = "/new-landing";
return NextResponse.rewrite(url);
}
return NextResponse.next();
}
Best Practices
-
Use multi-contexts to model real-world relationships (user, organization, device). This enables targeting at every level without duplicating attributes.
-
Bootstrap flags on the server for SSR applications. This eliminates the flash of default content while the client SDK initializes its streaming connection.
-
Keep flag lifecycles short. Create a flag, roll it out, confirm it works, then remove the flag and dead code path. Stale flags accumulate tech debt.
-
Use variation detail (
variationDetail) during debugging and experiment analysis to understand why a particular value was served. -
Track metrics close to the user action. Call
track()immediately when the event occurs rather than batching in application code — the SDK handles batching internally. -
Namespace flag keys by domain:
checkout-express-enabled,search-v2-algorithm,billing-retry-limit. This scales across teams. -
Set meaningful defaults. The fallback value in
variation()is used when the SDK cannot reach LaunchDarkly. Defaults should always be the safe, existing behavior. -
Use segments for reusable audiences. Define "beta testers" or "enterprise accounts" as segments in the dashboard, then reference them across multiple flags instead of duplicating targeting rules.
Anti-Patterns
-
Evaluating flags in tight loops. Flag evaluation is fast but not free. Cache the result in a local variable when evaluating inside loops or repeated renders.
-
Using flags as a database. Flags are for feature control, not persistent user preferences. Store user settings in your own data layer.
-
Anonymous contexts without stable keys. If you generate a new random key on every request, percentage rollouts become random rather than sticky. Use cookies or device IDs for consistency.
-
Ignoring flag prerequisites. If flag B depends on flag A being on, model this as a prerequisite in LaunchDarkly rather than nesting conditionals in code — the platform enforces the dependency.
-
Never cleaning up flags. Every flag that ships successfully should have a removal ticket. Use LaunchDarkly's code references and stale flag detection to find flags safe to remove.
-
Putting SDK keys in client bundles. The client-side ID is meant for browsers, but the server SDK key must stay on the server. Leaking it exposes your full flag configuration and targeting rules.
-
Wrapping the SDK in unnecessary abstractions. A thin wrapper for testability is fine, but deep abstraction layers hide the SDK's streaming and caching behavior, leading to stale evaluations or missed updates.
Install this skill directly: skilldb add feature-flags-services-skills
Related Skills
ConfigCat
"ConfigCat: feature flags and remote config with percentage rollouts, targeting rules, config-as-code, and cross-platform SDKs"
Flagsmith
"Flagsmith: open-source feature flags, remote config, segments, environments, audit logs, self-hosted, REST API"
Flipt
"Flipt: open-source, self-hosted feature flag platform with GitOps support, boolean and multivariate flags, and GRPC/REST APIs"
GrowthBook
"GrowthBook: open-source feature flags, A/B testing, Bayesian statistics, SDK, targeting, webhooks, self-hosted"
Split.io
"Split.io: feature delivery platform with feature flags, targeting, experimentation, traffic allocation, and metrics integration"
Statsig
"Statsig: feature gates, dynamic config, experiments/A/B tests, metrics, layers, Next.js SDK, server/client evaluation"