Feature flags let you decouple deployment from release. You can ship code behind flags and enable features gradually.
Install the SDK
pnpm add @launchdarkly/node-server-sdk launchdarkly-react-client-sdk
Server-Side Client
// lib/launchdarkly/server.ts
import * as LaunchDarkly from "@launchdarkly/node-server-sdk";
let client: LaunchDarkly.LDClient | null = null;
export async function getLDClient(): Promise<LaunchDarkly.LDClient> {
if (client) return client;
client = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY!);
await client.waitForInitialization();
return client;
}
export interface LDUser {
key: string;
email?: string;
name?: string;
custom?: Record<string, string | number | boolean>;
}
export async function getFlag<T>(
flagKey: string,
user: LDUser,
defaultValue: T
): Promise<T> {
const ld = await getLDClient();
const context: LaunchDarkly.LDContext = {
kind: "user",
key: user.key,
email: user.email,
name: user.name,
...user.custom,
};
return ld.variation(flagKey, context, defaultValue) as Promise<T>;
}
export async function getAllFlags(user: LDUser): Promise<Record<string, unknown>> {
const ld = await getLDClient();
const context: LaunchDarkly.LDContext = {
kind: "user",
key: user.key,
email: user.email,
name: user.name,
};
const allFlags = await ld.allFlagsState(context);
return allFlags.toJSON();
}
Server Component Usage
// app/(site)/pricing/page.tsx
import { getFlag } from "@/lib/launchdarkly/server";
import { auth } from "@/lib/auth";
import { PricingTable } from "@/components/PricingTable";
import { NewPricingTable } from "@/components/NewPricingTable";
export default async function PricingPage() {
const session = await auth();
const user = {
key: session?.user?.id ?? "anonymous",
email: session?.user?.email ?? undefined,
};
const showNewPricing = await getFlag<boolean>("new-pricing-page", user, false);
const discountPercentage = await getFlag<number>("discount-percentage", user, 0);
return (
<div className="max-w-5xl mx-auto py-16 px-4">
{showNewPricing ? (
<NewPricingTable discount={discountPercentage} />
) : (
<PricingTable />
)}
</div>
);
}
Client Provider
// components/providers/FeatureFlagProvider.tsx
"use client";
import { withLDProvider } from "launchdarkly-react-client-sdk";
function InnerProvider({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}
export const FeatureFlagProvider = withLDProvider({
clientSideID: process.env.NEXT_PUBLIC_LD_CLIENT_ID!,
reactOptions: {
useCamelCaseFlagKeys: true,
},
context: {
kind: "user",
key: "anonymous",
},
})(InnerProvider);
Client Component Usage
"use client";
import { useFlags, useLDClient } from "launchdarkly-react-client-sdk";
import { useEffect } from "react";
interface FeatureFlags {
showBetaBanner: boolean;
maxUploadSize: number;
enableDarkMode: boolean;
}
export function BetaBanner() {
const flags = useFlags() as FeatureFlags;
if (!flags.showBetaBanner) return null;
return (
<div className="bg-blue-50 border-b border-blue-200 py-2 text-center text-sm text-blue-800">
You are using a beta feature. Share feedback in our community.
</div>
);
}
export function useIdentifyUser(user: { id: string; email: string; plan: string }) {
const ldClient = useLDClient();
useEffect(() => {
if (!ldClient || !user.id) return;
ldClient.identify({
kind: "user",
key: user.id,
email: user.email,
plan: user.plan,
});
}, [ldClient, user.id, user.email, user.plan]);
}
Bootstrap Flags for SSR
Pass server-evaluated flags to the client to avoid flicker:
// app/layout.tsx
import { getAllFlags } from "@/lib/launchdarkly/server";
import { FeatureFlagProvider } from "@/components/providers/FeatureFlagProvider";
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const flags = await getAllFlags({ key: "anonymous" });
return (
<html lang="en">
<body>
<FeatureFlagProvider
options={{
bootstrap: flags,
}}
>
{children}
</FeatureFlagProvider>
</body>
</html>
);
}
Feature Flag Hook with Fallback
"use client";
import { useFlags } from "launchdarkly-react-client-sdk";
export function useFeatureFlag<T>(key: string, defaultValue: T): T {
const flags = useFlags();
// Handle SSR and loading states
if (typeof flags !== "object" || flags === null) {
return defaultValue;
}
const camelKey = key.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
return (flags as Record<string, unknown>)[camelKey] as T ?? defaultValue;
}
Gradual Rollout Pattern
export function FeatureGate({
flag,
children,
fallback = null,
}: {
flag: string;
children: React.ReactNode;
fallback?: React.ReactNode;
}) {
const enabled = useFeatureFlag(flag, false);
return enabled ? <>{children}</> : <>{fallback}</>;
}
// Usage
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<FeatureGate flag="new-analytics-widget">
<AnalyticsWidget />
</FeatureGate>
<FeatureGate
flag="ai-assistant"
fallback={<p className="text-sm text-muted-foreground">Coming soon</p>}
>
<AIAssistant />
</FeatureGate>
</div>
);
}
Need Gradual Feature Rollouts?
We help teams implement feature flagging and progressive delivery systems. Contact us to learn more.