From dff418da42658a242a248685131abd2a117e95ad Mon Sep 17 00:00:00 2001 From: Michael Anderjaska Date: Wed, 1 Jul 2026 11:22:31 -0400 Subject: [PATCH 1/5] making some fields optional --- flags-sdk/launchdarkly/.env.example | 21 ++- flags-sdk/launchdarkly/README.md | 45 +++++- .../launchdarkly/app/api/bootstrap/route.ts | 9 +- flags-sdk/launchdarkly/flags.ts | 2 +- .../launchdarkly/lib/launchdarkly-adapter.ts | 139 ++++++++++++++++++ flags-sdk/launchdarkly/package.json | 1 + flags-sdk/launchdarkly/pnpm-lock.yaml | 64 ++++++-- 7 files changed, 255 insertions(+), 26 deletions(-) create mode 100644 flags-sdk/launchdarkly/lib/launchdarkly-adapter.ts diff --git a/flags-sdk/launchdarkly/.env.example b/flags-sdk/launchdarkly/.env.example index d4a4508c76..1abf09af3f 100644 --- a/flags-sdk/launchdarkly/.env.example +++ b/flags-sdk/launchdarkly/.env.example @@ -1,13 +1,26 @@ -# For @flags-sdk/launchdarkly +# The Flags Explorer uses this to securely read and override flags. FLAGS_SECRET="" + +# Server-side flags are read in one of two modes. Configure ONE of them. + +# Mode 1 (recommended): Edge Config +# Flags are read from a Vercel Edge Config store that the LaunchDarkly +# Marketplace integration keeps in sync. Lowest latency. EDGE_CONFIG="" -LAUNCHDARKLY_PROJECT_SLUG="" LAUNCHDARKLY_CLIENT_SIDE_ID="" -# For launchdarkly-react-client-sdk +# Mode 2: LaunchDarkly server SDK +# Flags are read directly from LaunchDarkly using a server SDK key. +# No Edge Config required. Used automatically when EDGE_CONFIG is not set. +LAUNCHDARKLY_SDK_KEY="" + +# Required by the client-side React SDK (auto-injected by the integration). NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID="" -# For Flags Explorer +# Optional: links each flag to its details page in the LaunchDarkly console. +LAUNCHDARKLY_PROJECT_SLUG="" + +# Optional: lets the Flags Explorer fetch extra flag metadata from the LD API. LAUNCHDARKLY_API_KEY="" LAUNCHDARKLY_PROJECT_KEY="" LAUNCHDARKLY_ENVIRONMENT="" diff --git a/flags-sdk/launchdarkly/README.md b/flags-sdk/launchdarkly/README.md index 87532dc5d8..bab3edfa87 100644 --- a/flags-sdk/launchdarkly/README.md +++ b/flags-sdk/launchdarkly/README.md @@ -23,7 +23,16 @@ If you deployed your own and configured the feature flags on LaunchDarkly, you c The easiest way to get started with LaunchDarkly is through the integration in [Vercel Marketplace](https://vercel.com/marketplace/launchdarkly). -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fexamples%2Ftree%2Fmain%2Fflags-sdk%2Flaunchdarkly&env=FLAGS_SECRET&envDescription=The+FLAGS_SECRET+will+be+used+by+the+Flags+Explorer+to+securely+overwrite+feature+flags.+Must+be+32+random+bytes%2C+base64-encoded.+Use+the+generated+value+or+set+your+own.&envLink=https%3A%2F%2Fvercel.com%2Fdocs%2Fworkflow-collaboration%2Ffeature-flags%2Fsupporting-feature-flags%23flags_secret-environment-variable&project-name=launchdarkly-flags-sdk&repository-name=launchdarkly-flags-sdk) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fexamples%2Ftree%2Fmain%2Fflags-sdk%2Flaunchdarkly&env=FLAGS_SECRET&envDescription=The+FLAGS_SECRET+will+be+used+by+the+Flags+Explorer+to+securely+overwrite+feature+flags.+Must+be+32+random+bytes%2C+base64-encoded.+Use+the+generated+value+or+set+your+own.&envLink=https%3A%2F%2Fvercel.com%2Fdocs%2Fworkflow-collaboration%2Ffeature-flags%2Fsupporting-feature-flags%23flags_secret-environment-variable&project-name=launchdarkly-flags-sdk&repository-name=launchdarkly-flags-sdk&products=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22launchdarkly%22%2C%22productSlug%22%3A%22launchdarkly%22%2C%22protocol%22%3A%22experimentation%22%7D%5D) + +Clicking the button above will: + +1. Clone this repository to your GitHub account +2. Create a new Vercel project +3. Install the LaunchDarkly integration from the Vercel Marketplace, which connects your LaunchDarkly project and auto-injects `LAUNCHDARKLY_CLIENT_SIDE_ID` and `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID` +4. Prompt you to generate a `FLAGS_SECRET` for the Flags Explorer + +The integration does **not** set your server-side flag source (`EDGE_CONFIG` for Edge Config mode, or `LAUNCHDARKLY_SDK_KEY` for server SDK mode). After the project is created, choose a mode and set the remaining variables as described in [Step 5](#step-5-set-environment-variables). ### Step 1: Link the project @@ -85,18 +94,38 @@ Create the `Proceed to Checkout` experiment: After that, start the Experiment. -### Step 6: Set environment variables +### Step 5: Set environment variables + +The Marketplace integration auto-injects these into your project — you don't need to set them: + +- `LAUNCHDARKLY_CLIENT_SIDE_ID` — your LaunchDarkly environment's client-side ID +- `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID` — the same client-side ID, exposed to the browser + +This template reads server-side flags in one of two modes, and picks whichever you configure (Edge Config is preferred when both are set). Configure **one**: -See `.env.example` for a template. +- **Edge Config mode (recommended):** set `EDGE_CONFIG` (your Vercel Edge Config connection string). Flags are read from a Vercel Edge Config store that the Marketplace integration keeps in sync — the lowest-latency option. Requires `LAUNCHDARKLY_CLIENT_SIDE_ID`, which the integration already injects. +- **Server SDK mode:** set `LAUNCHDARKLY_SDK_KEY` (a server-side SDK key). Flags are read directly from LaunchDarkly, with no Edge Config required. Used automatically when `EDGE_CONFIG` is not set. -- [`FLAGS_SECRET`](https://vercel.com/docs/feature-flags/flags-explorer/reference#flags_secret-environment-variable) -- `EDGE_CONFIG` (Vercel Edge Config connection string) -- `LAUNCHDARKLY_PROJECT_SLUG` -- `LAUNCHDARKLY_CLIENT_SIDE_ID` -- `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID` (set to same value as `LAUNCHDARKLY_CLIENT_SIDE_ID`) +You also need to set (see `.env.example` for a template): + +- [`FLAGS_SECRET`](https://vercel.com/docs/feature-flags/flags-explorer/reference#flags_secret-environment-variable) — 32 random bytes, base64-encoded. The deploy button prompts you to generate this. +- _(Optional)_ `LAUNCHDARKLY_PROJECT_SLUG` — used only to link each flag to its details page in the LaunchDarkly console. _(Optional)_ If you provide the `LAUNCHDARKLY_API_KEY`, `LAUNCHDARKLY_PROJECT_KEY` and `LAUNCHDARKLY_ENVIRONMENT` environment variables, the Flags Explorer will fetch additional metadata from the LaunchDarkly API. This will show the description (if set) and displays a link to the feature flag on the LaunchDarkly Console. You can create an API key and find project and environment values in the [LaunchDarkly Console](https://app.launchdarkly.com/settings/projects). + +## Manual Setup (without the deploy button) + +If you don't use the deploy button above, you can set the project up by hand: + +1. Pick a server-side flag source: either install the [LaunchDarkly Vercel integration](https://launchdarkly.com/docs/integrations/vercel) so your flags are replicated into a Vercel Edge Config store (Edge Config mode), or grab a server-side SDK key from LaunchDarkly (server SDK mode). See [Step 5](#step-5-set-environment-variables). +2. Generate a `FLAGS_SECRET` yourself instead of relying on the deploy prompt: + + ```bash + node -e "console.log(crypto.randomBytes(32).toString('base64url'))" + ``` + +3. Set all of the environment variables listed in [Step 5](#step-5-set-environment-variables). diff --git a/flags-sdk/launchdarkly/app/api/bootstrap/route.ts b/flags-sdk/launchdarkly/app/api/bootstrap/route.ts index b5deea5001..bffc62b5b0 100644 --- a/flags-sdk/launchdarkly/app/api/bootstrap/route.ts +++ b/flags-sdk/launchdarkly/app/api/bootstrap/route.ts @@ -1,8 +1,13 @@ import { getStableId } from '@/utils/get-stable-id' -import { ldAdapter, type LDContext } from '@flags-sdk/launchdarkly' -import { NextResponse } from 'next/server' +import { ldAdapter, type LDContext } from '@/lib/launchdarkly-adapter' +import { connection, NextResponse } from 'next/server' export async function GET(request: Request): Promise { + // This route returns a per-user flag payload (keyed by the stable id cookie), + // so it must run per request. Marking it dynamic also avoids initializing the + // LaunchDarkly client at build time. + await connection() + await ldAdapter.ldClient.waitForInitialization() const stableId = await getStableId() diff --git a/flags-sdk/launchdarkly/flags.ts b/flags-sdk/launchdarkly/flags.ts index 3095e52863..808a919ce1 100644 --- a/flags-sdk/launchdarkly/flags.ts +++ b/flags-sdk/launchdarkly/flags.ts @@ -1,5 +1,5 @@ -import { ldAdapter, type LDContext } from '@flags-sdk/launchdarkly' import { flag } from 'flags/next' +import { ldAdapter, type LDContext } from './lib/launchdarkly-adapter' import { identify } from './lib/identify' export const showSummerBannerFlag = flag({ diff --git a/flags-sdk/launchdarkly/lib/launchdarkly-adapter.ts b/flags-sdk/launchdarkly/lib/launchdarkly-adapter.ts new file mode 100644 index 0000000000..f22d23dce6 --- /dev/null +++ b/flags-sdk/launchdarkly/lib/launchdarkly-adapter.ts @@ -0,0 +1,139 @@ +import type { Adapter } from 'flags' +import { + createLaunchDarklyAdapter, + type LDContext, +} from '@flags-sdk/launchdarkly' + +export type { LDContext } + +/** + * This adapter supports two ways of reading LaunchDarkly flags, and picks one + * automatically based on which environment variables are present: + * + * 1. Edge Config mode (`EDGE_CONFIG` + `LAUNCHDARKLY_CLIENT_SIDE_ID`): reads + * flags from a Vercel Edge Config store using `@flags-sdk/launchdarkly`. + * This is edge-native and the lowest latency, and is what the LaunchDarkly + * Vercel Marketplace integration is designed for. + * + * 2. Server SDK mode (`LAUNCHDARKLY_SDK_KEY`): talks to LaunchDarkly directly + * using `@launchdarkly/node-server-sdk`. This requires no Edge Config, so + * the template works with just the SDK key. + * + * Edge Config mode is preferred when both are configured. + */ + +const edgeConfigConnectionString = process.env.EDGE_CONFIG +const clientSideId = process.env.LAUNCHDARKLY_CLIENT_SIDE_ID +const projectSlug = process.env.LAUNCHDARKLY_PROJECT_SLUG +const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY + +/** Links a flag to its details page in the LaunchDarkly console. */ +function flagOrigin(key: string): string | undefined { + return projectSlug + ? `https://app.launchdarkly.com/projects/${projectSlug}/flags/${key}/` + : undefined +} + +/** The subset of the LaunchDarkly client this app relies on. */ +interface LDClientLike { + waitForInitialization(): Promise + allFlagsState( + context: LDContext, + ): Promise<{ toJSON(): unknown } | undefined> +} + +interface LDAdapterLike { + variation(): Adapter + ldClient: LDClientLike +} + +function createEdgeConfigAdapter(): LDAdapterLike { + return createLaunchDarklyAdapter({ + projectSlug: projectSlug ?? '', + clientSideId: clientSideId!, + edgeConfigConnectionString: edgeConfigConnectionString!, + }) as unknown as LDAdapterLike +} + +function createServerSdkAdapter(): LDAdapterLike { + // Lazily initialize a single client so we only connect when this mode runs. + let clientPromise: + | Promise + | undefined + + async function getClient() { + if (!clientPromise) { + clientPromise = import('@launchdarkly/node-server-sdk').then( + async ({ init }) => { + const client = init(sdkKey!) + await client.waitForInitialization({ timeout: 10 }) + return client + }, + ) + } + return clientPromise + } + + return { + variation(): Adapter { + return { + origin: flagOrigin, + async decide({ key, entities, defaultValue }) { + const client = await getClient() + const context: LDContext = + entities ?? { kind: 'user', key: 'anonymous', anonymous: true } + return client.variation( + key, + context, + defaultValue, + ) as Promise + }, + } + }, + ldClient: { + waitForInitialization: () => getClient(), + async allFlagsState(context: LDContext) { + const client = await getClient() + return (await client.allFlagsState(context)) as unknown as { + toJSON(): unknown + } + }, + }, + } +} + +let resolved: LDAdapterLike | undefined + +// Resolve the mode lazily (on first use) so importing this module never throws +// at build time when env vars are not yet available. +function adapter(): LDAdapterLike { + if (resolved) return resolved + if (edgeConfigConnectionString && clientSideId) { + resolved = createEdgeConfigAdapter() + } else if (sdkKey) { + resolved = createServerSdkAdapter() + } else { + throw new Error( + 'LaunchDarkly is not configured. Set EDGE_CONFIG and LAUNCHDARKLY_CLIENT_SIDE_ID ' + + 'for Edge Config mode, or LAUNCHDARKLY_SDK_KEY for server SDK mode.', + ) + } + return resolved +} + +export const ldAdapter: LDAdapterLike = { + variation(): Adapter { + let inner: Adapter | undefined + return { + origin: flagOrigin, + decide(options) { + if (!inner) inner = adapter().variation() + return inner.decide(options) + }, + } + }, + ldClient: { + waitForInitialization: () => adapter().ldClient.waitForInitialization(), + allFlagsState: (context) => adapter().ldClient.allFlagsState(context), + }, +} diff --git a/flags-sdk/launchdarkly/package.json b/flags-sdk/launchdarkly/package.json index 5de3ede56c..4f8bd6a8e2 100644 --- a/flags-sdk/launchdarkly/package.json +++ b/flags-sdk/launchdarkly/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@flags-sdk/launchdarkly": "^0.3.2", + "@launchdarkly/node-server-sdk": "^9.11.3", "@headlessui/react": "^2.2.0", "@heroicons/react": "2.2.0", "@tailwindcss/aspect-ratio": "0.4.2", diff --git a/flags-sdk/launchdarkly/pnpm-lock.yaml b/flags-sdk/launchdarkly/pnpm-lock.yaml index 4a72e95ca2..0868d17df6 100644 --- a/flags-sdk/launchdarkly/pnpm-lock.yaml +++ b/flags-sdk/launchdarkly/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@heroicons/react': specifier: 2.2.0 version: 2.2.0(react@19.2.3) + '@launchdarkly/node-server-sdk': + specifier: ^9.11.3 + version: 9.11.3 '@tailwindcss/aspect-ratio': specifier: 0.4.2 version: 0.4.2(tailwindcss@4.0.9) @@ -337,12 +340,21 @@ packages: '@launchdarkly/js-sdk-common@2.19.0': resolution: {integrity: sha512-p4MU3VxFSxj1T5yGm3OeJAS034nAiISGUF9Av3r9tClRCElcIdj159hADBYCE4VpiH6q+6KJ3cbvXF8v1200+g==} + '@launchdarkly/js-sdk-common@2.25.1': + resolution: {integrity: sha512-erG2RbA8QQMKW+D9Y7Uahez1dU+TzmyTzv9qlB7b0Pr/DS2SmUg5H8pJbkLcRrSxoshczZdB1UqsuE3RAk7UYQ==} + '@launchdarkly/js-server-sdk-common-edge@2.6.9': resolution: {integrity: sha512-z8rUtlnz527WJRa6IBAJ6SZjJUdbAFz+Fqdc4zZpFgxyYctEcNrrCua96P9IQlM7K+yZp9zggb7oBChPjixZ6g==} '@launchdarkly/js-server-sdk-common@2.16.2': resolution: {integrity: sha512-xjJP1YHdSABgn4whBccaIFK5Mr0e3WVdu47NJoSzbkVEDrr2LaOaya+92vYkuByulObvuGAkhI+ceJhz3G0l2g==} + '@launchdarkly/js-server-sdk-common@2.19.2': + resolution: {integrity: sha512-SZApHxn0PqYyRFfXtrJKQAGx6tlLD2sV0XvqOUkUdey1lKeObOL4hIw+70gQ3jrRAIvEH+SBC0uqlJ7uGlrRyA==} + + '@launchdarkly/node-server-sdk@9.11.3': + resolution: {integrity: sha512-Nhw7kZmTE14pzSEO06ttSjhAPeVVtFQoGbaJ33Bf3fxNPUCR8ZIBALzPVnZTTWz+hAqhrntMp8JzKcA7Bw6GCQ==} + '@launchdarkly/vercel-server-sdk@1.3.34': resolution: {integrity: sha512-uZjn6gH2wfct0HwiH+BH9EPJmLIsS5XmzMitFSWJjgosaVHG07qUWhZsQBbWpAIOz2Xc761lob5B4kpH9wJQdQ==} @@ -775,6 +787,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1385,6 +1401,10 @@ packages: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -1573,6 +1593,10 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + launchdarkly-eventsource@2.2.0: + resolution: {integrity: sha512-u38fYlLSq/m6oFz0MS1/76Sj2xzlYhTKZ+sf/vju6PA86PMc6fPlY5k8CdU79edLXjNwsvIQTDvDNy3llDqB8A==} + engines: {node: '>=0.12.0'} + launchdarkly-js-client-sdk@3.5.0: resolution: {integrity: sha512-3dgxC9S8K2ix6qjdArjZGOJPtAytgfQTuE+vWgjWJK7725rpYbuqbHghIFr5B0+WyWyVBYANldjWd1JdtYLwsw==} @@ -1980,11 +2004,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.7.1: - resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} @@ -2434,6 +2453,8 @@ snapshots: '@launchdarkly/js-sdk-common@2.19.0': {} + '@launchdarkly/js-sdk-common@2.25.1': {} + '@launchdarkly/js-server-sdk-common-edge@2.6.9': dependencies: '@launchdarkly/js-server-sdk-common': 2.16.2 @@ -2444,6 +2465,19 @@ snapshots: '@launchdarkly/js-sdk-common': 2.19.0 semver: 7.5.4 + '@launchdarkly/js-server-sdk-common@2.19.2': + dependencies: + '@launchdarkly/js-sdk-common': 2.25.1 + semver: 7.5.4 + + '@launchdarkly/node-server-sdk@9.11.3': + dependencies: + '@launchdarkly/js-server-sdk-common': 2.19.2 + https-proxy-agent: 7.0.6 + launchdarkly-eventsource: 2.2.0 + transitivePeerDependencies: + - supports-color + '@launchdarkly/vercel-server-sdk@1.3.34(next@16.0.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': dependencies: '@launchdarkly/js-server-sdk-common-edge': 2.6.9 @@ -2774,7 +2808,7 @@ snapshots: fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.7.1 + semver: 7.7.3 ts-api-utils: 2.0.1(typescript@5.7.3) typescript: 5.7.3 transitivePeerDependencies: @@ -2855,6 +2889,8 @@ snapshots: acorn@8.14.0: {} + agent-base@7.1.4: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3614,6 +3650,13 @@ snapshots: transitivePeerDependencies: - debug + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} ignore@5.3.2: {} @@ -3662,7 +3705,7 @@ snapshots: is-bun-module@1.3.0: dependencies: - semver: 7.7.1 + semver: 7.7.3 is-callable@1.2.7: {} @@ -3802,6 +3845,8 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 + launchdarkly-eventsource@2.2.0: {} + launchdarkly-js-client-sdk@3.5.0: dependencies: escape-string-regexp: 4.0.0 @@ -4178,10 +4223,7 @@ snapshots: dependencies: lru-cache: 6.0.0 - semver@7.7.1: {} - - semver@7.7.3: - optional: true + semver@7.7.3: {} set-function-length@1.2.2: dependencies: From 6289a25dcbb9964c31d8738190977de2fe436543 Mon Sep 17 00:00:00 2001 From: Michael Anderjaska Date: Wed, 1 Jul 2026 11:53:44 -0400 Subject: [PATCH 2/5] updates --- flags-sdk/launchdarkly/.gitignore | 3 +- flags-sdk/launchdarkly/README.md | 46 +++++++++++++++----- flags-sdk/launchdarkly/app/[code]/layout.tsx | 16 +++---- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/flags-sdk/launchdarkly/.gitignore b/flags-sdk/launchdarkly/.gitignore index fd3dbb571a..6512345595 100644 --- a/flags-sdk/launchdarkly/.gitignore +++ b/flags-sdk/launchdarkly/.gitignore @@ -26,7 +26,8 @@ yarn-debug.log* yarn-error.log* # local env files -.env*.local +.env* +!.env.example # vercel .vercel diff --git a/flags-sdk/launchdarkly/README.md b/flags-sdk/launchdarkly/README.md index bab3edfa87..f070197a55 100644 --- a/flags-sdk/launchdarkly/README.md +++ b/flags-sdk/launchdarkly/README.md @@ -29,10 +29,10 @@ Clicking the button above will: 1. Clone this repository to your GitHub account 2. Create a new Vercel project -3. Install the LaunchDarkly integration from the Vercel Marketplace, which connects your LaunchDarkly project and auto-injects `LAUNCHDARKLY_CLIENT_SIDE_ID` and `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID` +3. Install the LaunchDarkly integration from the Vercel Marketplace, which connects your LaunchDarkly project and auto-injects `LAUNCHDARKLY_CLIENT_SIDE_ID`, `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID`,and `LAUNCHDARKLY_SDK_KEY`. 4. Prompt you to generate a `FLAGS_SECRET` for the Flags Explorer -The integration does **not** set your server-side flag source (`EDGE_CONFIG` for Edge Config mode, or `LAUNCHDARKLY_SDK_KEY` for server SDK mode). After the project is created, choose a mode and set the remaining variables as described in [Step 5](#step-5-set-environment-variables). +The integration does **not** set your `EDGE_CONFIG`. See [Step 5](#step-5-set-environment-variables) for optional variables that you can set. ### Step 1: Link the project @@ -96,15 +96,10 @@ After that, start the Experiment. ### Step 5: Set environment variables -The Marketplace integration auto-injects these into your project — you don't need to set them: - -- `LAUNCHDARKLY_CLIENT_SIDE_ID` — your LaunchDarkly environment's client-side ID -- `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID` — the same client-side ID, exposed to the browser - This template reads server-side flags in one of two modes, and picks whichever you configure (Edge Config is preferred when both are set). Configure **one**: -- **Edge Config mode (recommended):** set `EDGE_CONFIG` (your Vercel Edge Config connection string). Flags are read from a Vercel Edge Config store that the Marketplace integration keeps in sync — the lowest-latency option. Requires `LAUNCHDARKLY_CLIENT_SIDE_ID`, which the integration already injects. -- **Server SDK mode:** set `LAUNCHDARKLY_SDK_KEY` (a server-side SDK key). Flags are read directly from LaunchDarkly, with no Edge Config required. Used automatically when `EDGE_CONFIG` is not set. +- **Edge Config mode (recommended):** set `EDGE_CONFIG` (your Vercel Edge Config connection string). Flags are read from a Vercel Edge Config store that the Marketplace integration keeps in sync — the lowest-latency option. +- **Server SDK mode (default):** Flags are read directly from LaunchDarkly, with no Edge Config required. Used automatically when `EDGE_CONFIG` is not set. You also need to set (see `.env.example` for a template): @@ -128,4 +123,35 @@ If you don't use the deploy button above, you can set the project up by hand: node -e "console.log(crypto.randomBytes(32).toString('base64url'))" ``` -3. Set all of the environment variables listed in [Step 5](#step-5-set-environment-variables). +3. Set the environment variables below (see `.env.example` for a template). Which ones you need depends on the server-side flag source you picked in step 1. + + **Always required:** + + | Variable | Description | + | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | + | `FLAGS_SECRET` | Used by the Flags Explorer to securely read and override flags. 32 random bytes, base64url-encoded (see step 2). | + | `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID` | Your LaunchDarkly client-side ID, used by the client-side React SDK. | + + **Edge Config mode** (recommended — set these if you installed the Vercel integration): + + | Variable | Description | + | ----------------------------- | ----------------------------------------------------------------------------------------------------- | + | `EDGE_CONFIG` | Connection string for the Vercel Edge Config store the integration keeps in sync. Preferred when set. | + | `LAUNCHDARKLY_CLIENT_SIDE_ID` | Your LaunchDarkly client-side ID, used to read flags from Edge Config. | + + **Server SDK mode** (set this instead if you're reading flags directly from LaunchDarkly): + + | Variable | Description | + | ---------------------- | ------------------------------------------------------------------------------------- | + | `LAUNCHDARKLY_SDK_KEY` | A LaunchDarkly server-side SDK key. Used automatically when `EDGE_CONFIG` is not set. | + + **Optional** (enable richer Flags Explorer metadata): + + | Variable | Description | + | --------------------------- | ---------------------------------------------------------------------------------------------------------- | + | `LAUNCHDARKLY_PROJECT_SLUG` | Links each flag to its details page in the LaunchDarkly console. | + | `LAUNCHDARKLY_API_KEY` | Lets the Flags Explorer fetch extra flag metadata (descriptions, console links) from the LaunchDarkly API. | + | `LAUNCHDARKLY_PROJECT_KEY` | Project the API key reads from. Required alongside `LAUNCHDARKLY_API_KEY`. | + | `LAUNCHDARKLY_ENVIRONMENT` | Environment the API key reads from. Required alongside `LAUNCHDARKLY_API_KEY`. | + + You can create an API key and find your project and environment values in the [LaunchDarkly Console](https://app.launchdarkly.com/settings/projects). diff --git a/flags-sdk/launchdarkly/app/[code]/layout.tsx b/flags-sdk/launchdarkly/app/[code]/layout.tsx index 58d9261914..646d2b4c24 100644 --- a/flags-sdk/launchdarkly/app/[code]/layout.tsx +++ b/flags-sdk/launchdarkly/app/[code]/layout.tsx @@ -15,8 +15,8 @@ export async function generateStaticParams() { // Instead of returning an empty array you could also call generatePermutations // to generate the permutations upfront. - const codes = await generatePermutations(productFlags); - return codes.map((code) => ({ code })); + const codes = await generatePermutations(productFlags) + return codes.map((code) => ({ code })) } export default async function Layout(props: { @@ -25,13 +25,13 @@ export default async function Layout(props: { code: string }> }) { - const params = await props.params; - const values = await deserialize(productFlags, params.code); - + const params = await props.params + const values = await deserialize(productFlags, params.code) + const showFreeDeliveryBanner = await showFreeDeliveryBannerFlag( params.code, - productFlags, - ); + productFlags + ) return ( @@ -47,5 +47,5 @@ export default async function Layout(props: { - ); + ) } From 167850680dc35314eb9da04123fdf47ee43f51a0 Mon Sep 17 00:00:00 2001 From: Michael Anderjaska Date: Wed, 1 Jul 2026 12:23:50 -0400 Subject: [PATCH 3/5] updated gitignore --- flags-sdk/launchdarkly/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/flags-sdk/launchdarkly/README.md b/flags-sdk/launchdarkly/README.md index f070197a55..90b82bfb52 100644 --- a/flags-sdk/launchdarkly/README.md +++ b/flags-sdk/launchdarkly/README.md @@ -32,7 +32,7 @@ Clicking the button above will: 3. Install the LaunchDarkly integration from the Vercel Marketplace, which connects your LaunchDarkly project and auto-injects `LAUNCHDARKLY_CLIENT_SIDE_ID`, `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID`,and `LAUNCHDARKLY_SDK_KEY`. 4. Prompt you to generate a `FLAGS_SECRET` for the Flags Explorer -The integration does **not** set your `EDGE_CONFIG`. See [Step 5](#step-5-set-environment-variables) for optional variables that you can set. +The integration does **not** set your `EDGE_CONFIG`. See [Step 5](#step-5-set-environment-variables) for other variables that you can set. ### Step 1: Link the project @@ -112,11 +112,11 @@ This will show the description (if set) and displays a link to the feature flag You can create an API key and find project and environment values in the [LaunchDarkly Console](https://app.launchdarkly.com/settings/projects). -## Manual Setup (without the deploy button) +## Manual Setup (without Marketplace integration) -If you don't use the deploy button above, you can set the project up by hand: +If you don't use the marketplace integration, you can set up this template by hand: -1. Pick a server-side flag source: either install the [LaunchDarkly Vercel integration](https://launchdarkly.com/docs/integrations/vercel) so your flags are replicated into a Vercel Edge Config store (Edge Config mode), or grab a server-side SDK key from LaunchDarkly (server SDK mode). See [Step 5](#step-5-set-environment-variables). +1. Pick a server-side flag source: either install the [LaunchDarkly Vercel integration](https://launchdarkly.com/docs/integrations/vercel) so your flags are replicated into a Vercel Edge Config store, or grab a server-side SDK key from LaunchDarkly (server SDK mode). See [Step 5](#step-5-set-environment-variables). 2. Generate a `FLAGS_SECRET` yourself instead of relying on the deploy prompt: ```bash @@ -132,7 +132,7 @@ If you don't use the deploy button above, you can set the project up by hand: | `FLAGS_SECRET` | Used by the Flags Explorer to securely read and override flags. 32 random bytes, base64url-encoded (see step 2). | | `NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_SIDE_ID` | Your LaunchDarkly client-side ID, used by the client-side React SDK. | - **Edge Config mode** (recommended — set these if you installed the Vercel integration): + **Edge Config mode** (recommended — set these if you are not using the marketplace integration): | Variable | Description | | ----------------------------- | ----------------------------------------------------------------------------------------------------- | From f8ad9671834a26b9b42a335d7459d928f5443ebe Mon Sep 17 00:00:00 2001 From: Michael Anderjaska Date: Wed, 1 Jul 2026 12:31:41 -0400 Subject: [PATCH 4/5] update readme with correct toggle text --- flags-sdk/launchdarkly/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flags-sdk/launchdarkly/README.md b/flags-sdk/launchdarkly/README.md index 90b82bfb52..736eb2396a 100644 --- a/flags-sdk/launchdarkly/README.md +++ b/flags-sdk/launchdarkly/README.md @@ -56,7 +56,7 @@ vercel env pull Head over to the [LaunchDarkly Console](https://app.launchdarkly.com) and create the feature flags and experiments required by this template. -Be sure to enable the `SDKs using Client-side ID` option for each Feature Flag. +Be sure to enable the `Available on client-side SDKs` option for each Feature Flag. Feature Flags: From d5de0688b370cf188c907bda8496479f6bc70dbf Mon Sep 17 00:00:00 2001 From: Michael Anderjaska Date: Wed, 1 Jul 2026 13:14:45 -0400 Subject: [PATCH 5/5] look for EXPERIMENATION_CONFIG --- flags-sdk/launchdarkly/.env.example | 7 +++- flags-sdk/launchdarkly/README.md | 19 +++++----- .../launchdarkly/lib/launchdarkly-adapter.ts | 37 ++++++++++++------- 3 files changed, 39 insertions(+), 24 deletions(-) diff --git a/flags-sdk/launchdarkly/.env.example b/flags-sdk/launchdarkly/.env.example index 1abf09af3f..759692bb1c 100644 --- a/flags-sdk/launchdarkly/.env.example +++ b/flags-sdk/launchdarkly/.env.example @@ -6,12 +6,17 @@ FLAGS_SECRET="" # Mode 1 (recommended): Edge Config # Flags are read from a Vercel Edge Config store that the LaunchDarkly # Marketplace integration keeps in sync. Lowest latency. +# Set EITHER EDGE_CONFIG or EXPERIMENTATION_CONFIG (both are Edge Config +# connection strings). The Marketplace integration injects EXPERIMENTATION_CONFIG +# when you enable Edge Config syncing; EDGE_CONFIG takes precedence if both are set. EDGE_CONFIG="" +EXPERIMENTATION_CONFIG="" LAUNCHDARKLY_CLIENT_SIDE_ID="" # Mode 2: LaunchDarkly server SDK # Flags are read directly from LaunchDarkly using a server SDK key. -# No Edge Config required. Used automatically when EDGE_CONFIG is not set. +# No Edge Config required. Used automatically when neither EDGE_CONFIG nor +# EXPERIMENTATION_CONFIG is set. LAUNCHDARKLY_SDK_KEY="" # Required by the client-side React SDK (auto-injected by the integration). diff --git a/flags-sdk/launchdarkly/README.md b/flags-sdk/launchdarkly/README.md index 736eb2396a..f2078a521e 100644 --- a/flags-sdk/launchdarkly/README.md +++ b/flags-sdk/launchdarkly/README.md @@ -98,8 +98,8 @@ After that, start the Experiment. This template reads server-side flags in one of two modes, and picks whichever you configure (Edge Config is preferred when both are set). Configure **one**: -- **Edge Config mode (recommended):** set `EDGE_CONFIG` (your Vercel Edge Config connection string). Flags are read from a Vercel Edge Config store that the Marketplace integration keeps in sync — the lowest-latency option. -- **Server SDK mode (default):** Flags are read directly from LaunchDarkly, with no Edge Config required. Used automatically when `EDGE_CONFIG` is not set. +- **Edge Config mode (recommended):** set `EDGE_CONFIG` **or** `EXPERIMENTATION_CONFIG` (both are Vercel Edge Config connection strings). Flags are read from a Vercel Edge Config store that the Marketplace integration keeps in sync — the lowest-latency option. The Marketplace integration injects `EXPERIMENTATION_CONFIG` when you enable Edge Config syncing; `EDGE_CONFIG` takes precedence if both are set. +- **Server SDK mode (default):** Flags are read directly from LaunchDarkly, with no Edge Config required. Used automatically when neither `EDGE_CONFIG` nor `EXPERIMENTATION_CONFIG` is set. You also need to set (see `.env.example` for a template): @@ -134,16 +134,17 @@ If you don't use the marketplace integration, you can set up this template by ha **Edge Config mode** (recommended — set these if you are not using the marketplace integration): - | Variable | Description | - | ----------------------------- | ----------------------------------------------------------------------------------------------------- | - | `EDGE_CONFIG` | Connection string for the Vercel Edge Config store the integration keeps in sync. Preferred when set. | - | `LAUNCHDARKLY_CLIENT_SIDE_ID` | Your LaunchDarkly client-side ID, used to read flags from Edge Config. | + | Variable | Description | + | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `EDGE_CONFIG` | Connection string for the Vercel Edge Config store the integration keeps in sync. Preferred when set. | + | `EXPERIMENTATION_CONFIG` | Alternative Edge Config connection string. Injected by the Marketplace integration when Edge Config syncing is enabled. Used when `EDGE_CONFIG` is not set. | + | `LAUNCHDARKLY_CLIENT_SIDE_ID` | Your LaunchDarkly client-side ID, used to read flags from Edge Config. | **Server SDK mode** (set this instead if you're reading flags directly from LaunchDarkly): - | Variable | Description | - | ---------------------- | ------------------------------------------------------------------------------------- | - | `LAUNCHDARKLY_SDK_KEY` | A LaunchDarkly server-side SDK key. Used automatically when `EDGE_CONFIG` is not set. | + | Variable | Description | + | ---------------------- | ---------------------------------------------------------------------------------------------------------------------- | + | `LAUNCHDARKLY_SDK_KEY` | A LaunchDarkly server-side SDK key. Used automatically when neither `EDGE_CONFIG` nor `EXPERIMENTATION_CONFIG` is set. | **Optional** (enable richer Flags Explorer metadata): diff --git a/flags-sdk/launchdarkly/lib/launchdarkly-adapter.ts b/flags-sdk/launchdarkly/lib/launchdarkly-adapter.ts index f22d23dce6..edfb7bcaa6 100644 --- a/flags-sdk/launchdarkly/lib/launchdarkly-adapter.ts +++ b/flags-sdk/launchdarkly/lib/launchdarkly-adapter.ts @@ -10,10 +10,13 @@ export type { LDContext } * This adapter supports two ways of reading LaunchDarkly flags, and picks one * automatically based on which environment variables are present: * - * 1. Edge Config mode (`EDGE_CONFIG` + `LAUNCHDARKLY_CLIENT_SIDE_ID`): reads - * flags from a Vercel Edge Config store using `@flags-sdk/launchdarkly`. - * This is edge-native and the lowest latency, and is what the LaunchDarkly - * Vercel Marketplace integration is designed for. + * 1. Edge Config mode (`EDGE_CONFIG` or `EXPERIMENTATION_CONFIG`, plus + * `LAUNCHDARKLY_CLIENT_SIDE_ID`): reads flags from a Vercel Edge Config + * store using `@flags-sdk/launchdarkly`. This is edge-native and the lowest + * latency, and is what the LaunchDarkly Vercel Marketplace integration is + * designed for. The Marketplace integration injects the connection string + * as `EXPERIMENTATION_CONFIG` when Edge Config syncing is enabled; a + * manually-provisioned store is typically `EDGE_CONFIG`. Either works. * * 2. Server SDK mode (`LAUNCHDARKLY_SDK_KEY`): talks to LaunchDarkly directly * using `@launchdarkly/node-server-sdk`. This requires no Edge Config, so @@ -22,7 +25,11 @@ export type { LDContext } * Edge Config mode is preferred when both are configured. */ -const edgeConfigConnectionString = process.env.EDGE_CONFIG +// The Marketplace integration provides the Edge Config connection string as +// `EXPERIMENTATION_CONFIG`; a manually-created store is usually `EDGE_CONFIG`. +// Both are valid Edge Config URLs, so accept either (EDGE_CONFIG wins if set). +const edgeConfigConnectionString = + process.env.EDGE_CONFIG ?? process.env.EXPERIMENTATION_CONFIG const clientSideId = process.env.LAUNCHDARKLY_CLIENT_SIDE_ID const projectSlug = process.env.LAUNCHDARKLY_PROJECT_SLUG const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY @@ -37,9 +44,7 @@ function flagOrigin(key: string): string | undefined { /** The subset of the LaunchDarkly client this app relies on. */ interface LDClientLike { waitForInitialization(): Promise - allFlagsState( - context: LDContext, - ): Promise<{ toJSON(): unknown } | undefined> + allFlagsState(context: LDContext): Promise<{ toJSON(): unknown } | undefined> } interface LDAdapterLike { @@ -68,7 +73,7 @@ function createServerSdkAdapter(): LDAdapterLike { const client = init(sdkKey!) await client.waitForInitialization({ timeout: 10 }) return client - }, + } ) } return clientPromise @@ -80,12 +85,15 @@ function createServerSdkAdapter(): LDAdapterLike { origin: flagOrigin, async decide({ key, entities, defaultValue }) { const client = await getClient() - const context: LDContext = - entities ?? { kind: 'user', key: 'anonymous', anonymous: true } + const context: LDContext = entities ?? { + kind: 'user', + key: 'anonymous', + anonymous: true, + } return client.variation( key, context, - defaultValue, + defaultValue ) as Promise }, } @@ -114,8 +122,9 @@ function adapter(): LDAdapterLike { resolved = createServerSdkAdapter() } else { throw new Error( - 'LaunchDarkly is not configured. Set EDGE_CONFIG and LAUNCHDARKLY_CLIENT_SIDE_ID ' + - 'for Edge Config mode, or LAUNCHDARKLY_SDK_KEY for server SDK mode.', + 'LaunchDarkly is not configured. Set EDGE_CONFIG (or EXPERIMENTATION_CONFIG) ' + + 'and LAUNCHDARKLY_CLIENT_SIDE_ID for Edge Config mode, or LAUNCHDARKLY_SDK_KEY ' + + 'for server SDK mode.' ) } return resolved