diff --git a/package.json b/package.json index aaedd7dfc..4fc0b4a9f 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "packages/sdk/react/contract-tests", "packages/sdk/react/examples/hello-react", "packages/sdk/react/examples/server-only", + "packages/sdk/react/examples/vercel-edge", "packages/sdk/react-native", "packages/sdk/react-native/example", "packages/sdk/react-native/contract-tests/entity", diff --git a/packages/sdk/react/examples/README.md b/packages/sdk/react/examples/README.md index 0d75b91da..e36894ed1 100644 --- a/packages/sdk/react/examples/README.md +++ b/packages/sdk/react/examples/README.md @@ -6,3 +6,4 @@ This directory contains example applications demonstrating the LaunchDarkly Reac |---------|-------------| | [hello-react](./hello-react/) | Minimal Vite + React app that evaluates a boolean feature flag and displays the result with real-time updates. This is the recommended starting point. | | [server-only](./server-only/) | Next.js App Router example demonstrating server-side flag evaluation with React Server Components. | +| [vercel-edge](./vercel-edge/) | Next.js App Router example using the Vercel Edge SDK to evaluate flags from Vercel Edge Config, with server-to-client bootstrap via `LDIsomorphicProvider`. | diff --git a/packages/sdk/react/examples/vercel-edge/.gitignore b/packages/sdk/react/examples/vercel-edge/.gitignore new file mode 100644 index 000000000..077d13c7b --- /dev/null +++ b/packages/sdk/react/examples/vercel-edge/.gitignore @@ -0,0 +1,43 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage +/test-results + +# next.js +/.next/ +/out/ +/.swc/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/packages/sdk/react/examples/vercel-edge/README.md b/packages/sdk/react/examples/vercel-edge/README.md new file mode 100644 index 000000000..0274cbc94 --- /dev/null +++ b/packages/sdk/react/examples/vercel-edge/README.md @@ -0,0 +1,84 @@ +# LaunchDarkly sample React + Vercel Edge application + +We've built a simple web application that demonstrates how the LaunchDarkly React SDK works with +the Vercel Edge SDK. The app evaluates feature flags using data stored in +[Vercel Edge Config](https://vercel.com/docs/edge-config/overview) and renders the result using +React Server Components. + +The Vercel SDK reads flag data from Edge Config instead of connecting to LaunchDarkly servers +directly, providing ultra-low latency flag evaluation at the edge. + +The demo shows 2 ways to use React server-side rendering: + +1. Using `createLDServerSession` and `useLDServerSession` to provide +per-request session isolation: Nested Server Components access the session through React's `cache()` +without any prop drilling. + +2. Using the `LDIsomorphicProvider` to bootstrap the browser SDK with server-evaluated flag values. +This allows the browser SDK to start immediately with real values. + +Below, you'll find the build procedure. For more comprehensive instructions, you can visit your +[Quickstart page](https://app.launchdarkly.com/quickstart#/) or the +[React SDK reference guide](https://docs.launchdarkly.com/sdk/client-side/react/react-web). + +This demo requires Node.js 18 or higher. + +## Prerequisites + +This example requires the [LaunchDarkly Vercel integration](https://vercel.com/integrations/launchdarkly) +to be configured. The integration syncs your LaunchDarkly flag data to Vercel Edge Config so that +the Vercel SDK can read it without connecting to LaunchDarkly servers. + +## Build instructions + +1. Set the `VERCEL_EDGE_CONFIG` environment variable to your Vercel Edge Config connection string. + You can find this in your Vercel project settings under Edge Config. + + ```bash + export VERCEL_EDGE_CONFIG="https://edge-config.vercel.com/ecfg_..." + ``` + +2. Set the `LD_CLIENT_SIDE_ID` environment variable to your LaunchDarkly client-side ID. + The Vercel SDK uses this to look up flag data in Edge Config, and the same value is used + to bootstrap the browser SDK. + + ```bash + export LD_CLIENT_SIDE_ID="my-client-side-id" + ``` + +3. If there is an existing boolean feature flag in your LaunchDarkly project that you want to + evaluate, set `LAUNCHDARKLY_FLAG_KEY`: + + ```bash + export LAUNCHDARKLY_FLAG_KEY="my-flag-key" + ``` + + Otherwise, `sample-feature` will be used by default. + +## Running + +On the command line, run: + +```bash +yarn dev +``` + +Then open [http://localhost:3000](http://localhost:3000) in your browser. You will see the +spec message, current context name, and a full-page background: green when the flag is on, +or grey when off. + +To simulate a different user, append the `?context=` query parameter. Each tab gets a +completely independent `LDServerSession` with its own context: + +| URL | Context | +|-----|---------| +| `http://localhost:3000/` | Sandy (example-user-key) — default | +| `http://localhost:3000/?context=sandy` | Sandy (example-user-key) | +| `http://localhost:3000/?context=jamie` | Jamie (user-jamie) | +| `http://localhost:3000/?context=alex` | Alex (user-alex) | + +If you have targeting rules in LaunchDarkly that serve different values to different user keys, +you will see different flag results for each context. + +In a production app, the user identity would come from auth tokens, cookies, or session data +instead of query parameters. diff --git a/packages/sdk/react/examples/vercel-edge/app/App.tsx b/packages/sdk/react/examples/vercel-edge/app/App.tsx new file mode 100644 index 000000000..a7fe2b2a9 --- /dev/null +++ b/packages/sdk/react/examples/vercel-edge/app/App.tsx @@ -0,0 +1,41 @@ +import { useLDServerSession } from '@launchdarkly/react-sdk/server'; + +import BootstrappedClient from './BootstrappedClient'; + +// The flag key to evaluate. Override with the LAUNCHDARKLY_FLAG_KEY environment variable. +const flagKey = process.env.LAUNCHDARKLY_FLAG_KEY || 'sample-feature'; + +export default async function App() { + // The session was stored here by createLDServerSession() in the parent page. + const session = useLDServerSession(); + + if (!session) { + return ( +
+ No LaunchDarkly session found. Ensure createLDServerSession() is called before rendering + this component. +
+ ); + } + + const flagValue = await session.boolVariation(flagKey, false); + const ctx = session.getContext() as { name?: string; key: string }; + + console.log('[LaunchDarkly] Flag evaluation:', { + flagKey, + flagValue, + context: session.getContext(), + }); + + return ( +Feature flag: {flagKey}
+Context: {ctx.name ?? ctx.key}
++ Server: feature flag evaluates to {String(flagValue)} (server-side + rendered). +
++ Client: feature flag evaluates to {String(flagValue)} (bootstrapped). +
+ ); +} diff --git a/packages/sdk/react/examples/vercel-edge/app/layout.tsx b/packages/sdk/react/examples/vercel-edge/app/layout.tsx new file mode 100644 index 000000000..e3006e4d8 --- /dev/null +++ b/packages/sdk/react/examples/vercel-edge/app/layout.tsx @@ -0,0 +1,13 @@ +import './styles.css'; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/packages/sdk/react/examples/vercel-edge/app/page.tsx b/packages/sdk/react/examples/vercel-edge/app/page.tsx new file mode 100644 index 000000000..97bedb44a --- /dev/null +++ b/packages/sdk/react/examples/vercel-edge/app/page.tsx @@ -0,0 +1,81 @@ +import { createClient } from '@vercel/edge-config'; + +import { createLDServerSession, LDIsomorphicProvider } from '@launchdarkly/react-sdk/server'; +import { init } from '@launchdarkly/vercel-server-sdk'; + +import App from './App'; + +// The Vercel SDK reads flag data from Vercel Edge Config instead of connecting +// to LaunchDarkly servers directly, so it uses the client-side ID — not the +// server SDK key. +const clientSideId = process.env.LD_CLIENT_SIDE_ID || ''; +const edgeConfig = process.env.VERCEL_EDGE_CONFIG; +const edgeConfigClient = edgeConfig ? createClient(edgeConfig) : null; +const ldBaseClient = clientSideId && edgeConfigClient ? init(clientSideId, edgeConfigClient) : null; + +// Select via ?context=sandy|jamie|alex (defaults to sandy). +const PRESET_CONTEXTS = { + sandy: { kind: 'user' as const, key: 'example-user-key', name: 'Sandy' }, + jamie: { kind: 'user' as const, key: 'user-jamie', name: 'Jamie' }, + alex: { kind: 'user' as const, key: 'user-alex', name: 'Alex' }, +}; + +export default async function Home({ + searchParams, +}: { + searchParams: Promise<{ context?: string }>; +}) { + if (!edgeConfigClient) { + return ( ++ Vercel Edge Config is required: set the VERCEL_EDGE_CONFIG environment variable and try + again. +
++ LaunchDarkly client-side ID is required: set the LD_CLIENT_SIDE_ID environment variable + and try again. +
++ SDK failed to initialize. Please check your Edge Config connection and LaunchDarkly + client-side ID for any issues. +
+