Skip to content

Commit feb0bf7

Browse files
committed
refactor(vercel): change complete example to conform to specs
1 parent 247ae7c commit feb0bf7

30 files changed

Lines changed: 409 additions & 534 deletions

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"packages/sdk/react-native/example-fdv2",
2929
"packages/sdk/react-native/contract-tests/entity",
3030
"packages/sdk/vercel",
31+
"packages/sdk/vercel/examples/complete",
3132
"packages/sdk/svelte",
3233
"packages/sdk/svelte/example",
3334
"packages/sdk/akamai-base",

packages/sdk/react/examples/vercel-edge/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "@internal/react-sdk-example-vercel-edge",
2+
"name": "@launchdarkly/react-sdk-example-vercel-edge",
33
"private": true,
44
"scripts": {
55
"dev": "next dev",
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
LD_CLIENT_SIDE_ID=
2+
EDGE_CONFIG=
3+
LAUNCHDARKLY_FLAG_KEY=sample-feature
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Mock mode: uses the built-in mock Edge Config server.
2+
# Copy this file to .env.local to try the example without a Vercel account.
3+
EDGE_CONFIG=http://localhost:3000/api/edge-config-mock/ecfg_mock?token=mock&id=ecfg_mock
4+
LD_CLIENT_SIDE_ID=mock-client-side-id
5+
LAUNCHDARKLY_FLAG_KEY=sample-feature

packages/sdk/vercel/examples/complete/.eslintrc.json

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 12 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,21 @@
1-
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2-
3-
# Dependencies
1+
# dependencies
42
/node_modules
5-
/.pnp
6-
.pnp.js
7-
8-
# Testing
9-
/coverage
103

11-
# Next.js
4+
# next.js
125
/.next/
136
/out/
147

15-
# VS code
16-
/.vscode
17-
18-
# Production
19-
/build
20-
21-
# Misc
22-
.DS_Store
23-
*.pem
24-
25-
# Debug
26-
npm-debug.log*
27-
yarn-debug.log*
28-
yarn-error.log*
29-
30-
# Local ENV files
31-
.env.local
32-
.env.development.local
33-
.env.test.local
34-
.env.production.local
8+
# env files
9+
.env*
10+
!.env.example
11+
!.env.mock
3512

36-
# Vercel
13+
# vercel
3714
.vercel
3815

39-
# Turborepo
40-
.turbo
41-
4216
# typescript
43-
*.tsbuildinfo
17+
*.tsbuildinfo
18+
next-env.d.ts
19+
20+
# misc
21+
.DS_Store

packages/sdk/vercel/examples/complete/.npmrc

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,105 @@
1-
# Complete example app for Vercel LaunchDarkly SDK
1+
# LaunchDarkly sample Vercel application
22

3-
This example shows how to evaluate feature flags in Vercel's edge runtime using the [LaunchDarkly Vercel SDK](https://github.com/launchdarkly/js-core/tree/main/packages/sdk/vercel). Two primary use cases are highlighted:
3+
This example shows how to evaluate feature flags in Vercel's edge runtime using the
4+
[LaunchDarkly Vercel SDK](https://github.com/launchdarkly/js-core/tree/main/packages/sdk/vercel).
5+
Two primary use cases are highlighted:
46

5-
1. Bootstrapping feature flags from the edge runtime and consuming them in the [LaunchDarkly Client-side SDK for React](https://github.com/launchdarkly/react-client-sdk). This is leveraging feature flags in edge-rendered pages while still maintaining the events and ergonomics provided by the React SDK. You can see details in [`app/layout.tsx`](./app/layout.tsx) and [`components/launchdarklyProvider.tsx`](./components/launchdarklyProvider.tsx).
6-
2. Evaluating feature flags in the [Edge Middleware](https://vercel.com/docs/concepts/functions/edge-middleware). This can be seen in [`middleware.ts`](./middleware.ts).
7+
1. **Edge Middleware** ([`proxy.ts`](./proxy.ts)) — evaluates a feature flag per request
8+
and attaches the result as a header for server-rendering.
9+
2. **Edge Route Handler** ([`app/api/flag/route.ts`](./app/api/flag/route.ts)) — evaluates a
10+
feature flag and returns JSON, used by the client to poll for live updates.
711

8-
## Demo
9-
10-
https://hello-vercel-edge.vercel.app/
12+
Both share a single edge client defined in [`lib/ldEdgeClient.ts`](./lib/ldEdgeClient.ts).
1113

1214
## Local development
1315

14-
#### Create a new LaunchDarkly project and flags
16+
#### Create a new LaunchDarkly project and flag
1517

16-
For simplicity, we recommend [creating a new LaunchDarkly project](https://docs.launchdarkly.com/home/organize/projects/?q=create+proj) for this example app. After creating a new project, create the following feature flags with Client-side SDK availability:
18+
For simplicity, we recommend
19+
[creating a new LaunchDarkly project](https://docs.launchdarkly.com/home/organize/projects/?q=create+proj)
20+
for this example app. After creating a new project, create a single boolean feature flag with
21+
client-side SDK availability:
1722

18-
- `bootstrap-flags` - (Boolean) - This flag will determine whether or not the LaunchDarkly React SDK will bootstrap feature flags from the edge.
19-
- `show-debugging-info` - (Boolean) - This flag is used to expose the current flag values.
20-
- `hero-text` - (String) - This flag is used to dynamically change the hero text. You can make the variations anything you want, e.g. "The best way to buy the products you love."
21-
- `enable-hot-dog-favicon` - (Boolean) - This flag is used in middleware.ts to dynamically load a different favicon.
22-
- `store-closed` - (Boolean) - This flag is evaluated in `middleware.ts` and can be used to load a different home page when the store is closed.
23+
- `sample-feature` — (Boolean) the flag this example evaluates and renders.
2324

2425
#### Set up the LaunchDarkly Vercel integration
2526

26-
You will need to have the LaunchDarkly Vercel integration configured to push feature flag data to your Vercel Edge Config. Read the [Vercel documentation](https://docs.launchdarkly.com/integrations/vercel/) to set up the integration. Be sure to connect the project you created above.
27+
You will need to have the LaunchDarkly Vercel integration configured to push feature flag data to
28+
your Vercel Edge Config. Read the
29+
[Vercel documentation](https://docs.launchdarkly.com/integrations/vercel/) to set up the
30+
integration. Be sure to connect the project you created above.
2731

2832
#### Set up environment variables
2933

30-
1. Copy this directory in a new repository.
34+
1. Copy this directory into a new repository.
3135
2. Create a new Vercel project based on the new repository.
32-
3. [Add a new environment variable to your project](https://vercel.com/docs/concepts/projects/environment-variables) named `LD_CLIENT_SIDE_ID` and set it to the LaunchDarkly client-side ID for the **Test** environment in the project you created above.
33-
4. Follow [Vercel's documentation](https://vercel.com/docs/storage/edge-config/get-started) to connect an Edge Config to your new project.
36+
3. [Add a new environment variable to your project](https://vercel.com/docs/concepts/projects/environment-variables)
37+
named `LD_CLIENT_SIDE_ID` and set it to the LaunchDarkly client-side ID for the **Test**
38+
environment in the project you created above.
39+
4. Follow [Vercel's documentation](https://vercel.com/docs/storage/edge-config/get-started) to
40+
connect an Edge Config to your new project.
3441
5. Run the following command to link your local codebase to your Vercel project:
3542

36-
```shell
37-
vercel link
38-
```
43+
```shell
44+
vercel link
45+
```
3946

40-
6. Run the following command to sync your projects environment variables in your development environment:
47+
6. Run the following command to sync your project's environment variables in your development
48+
environment:
4149

42-
```shell
43-
vercel env pull .env.development.local
44-
```
50+
```shell
51+
vercel env pull .env.development.local
52+
```
4553

46-
7. After completing the guide above, you should have linked this example app to your Vercel project and created an `.env.development.local`.
47-
8. Verify the contents of `.env.development.local` have values for the `LD_CLIENT_SIDE_ID` and `EDGE_CONFIG`.
54+
7. After completing the steps above, you should have linked this example app to your Vercel
55+
project and created a `.env.development.local`.
56+
8. Verify the contents of `.env.development.local` have values for `LD_CLIENT_SIDE_ID` and
57+
`EDGE_CONFIG`.
4858
9. Run the following command to install all dependencies:
4959

50-
```shell
51-
yarn
52-
```
60+
```shell
61+
yarn
62+
```
5363

5464
10. Run the following command to start your development environment:
5565

66+
```shell
67+
yarn dev
68+
```
69+
70+
Open [http://localhost:3000](http://localhost:3000). You should see:
71+
72+
- **Green background** (`#00844B`) when the flag evaluates to `true`
73+
- **Dark background** (`#373841`) when the flag evaluates to `false`
74+
- The message: "The sample-feature feature flag evaluates to true/false."
75+
76+
The page polls `/api/flag` every 2 seconds. Toggle the flag in LaunchDarkly and the background
77+
color will update automatically.
78+
79+
## Optional: try it without a Vercel account
80+
81+
If you want to run the example locally without setting up the LaunchDarkly Vercel integration, the
82+
example includes a built-in mock Edge Config server. Copy the pre-configured mock environment file
83+
and start the dev server:
84+
5685
```shell
86+
cp .env.mock .env.local
5787
yarn dev
5888
```
89+
90+
Mock mode uses the
91+
[external connection string](https://vercel.com/docs/edge-config) feature of `@vercel/edge-config`
92+
to point the SDK at a local API route ([`app/api/edge-config-mock/[...path]/route.ts`](./app/api/edge-config-mock/%5B...path%5D/route.ts))
93+
that returns a static flag payload. This is for local development only — production deployments
94+
should use a real Vercel Edge Config.
95+
96+
## How it works
97+
98+
| Path | Purpose |
99+
|------|---------|
100+
| [`proxy.ts`](./proxy.ts) | Edge Middleware that evaluates the flag and sets a header for server-rendering. |
101+
| [`app/api/flag/route.ts`](./app/api/flag/route.ts) | Edge Route Handler that evaluates the flag and returns JSON for client polling. |
102+
| [`app/page.tsx`](./app/page.tsx) | Server component that reads the middleware header for the initial render. |
103+
| [`app/FlagDisplay.tsx`](./app/FlagDisplay.tsx) | Client component that polls `/api/flag` every 2 seconds for live updates. |
104+
| [`lib/ldEdgeClient.ts`](./lib/ldEdgeClient.ts) | Lazily-initialized LaunchDarkly Vercel SDK client shared by middleware and route handler. |
105+
| [`app/api/edge-config-mock/`](./app/api/edge-config-mock/) | Mock Edge Config server for local development without a Vercel account. |
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use client';
2+
3+
import { useCallback, useEffect, useState } from 'react';
4+
5+
interface FlagState {
6+
flagKey: string;
7+
flagValue: boolean;
8+
}
9+
10+
export default function FlagDisplay({ initialState }: { initialState?: FlagState }) {
11+
const [state, setState] = useState<FlagState | null>(initialState ?? null);
12+
const [error, setError] = useState<string | null>(null);
13+
14+
const fetchFlag = useCallback(async () => {
15+
try {
16+
const res = await fetch('/api/flag');
17+
const data = await res.json();
18+
19+
if (data.error) {
20+
setError(data.error);
21+
return;
22+
}
23+
24+
// Only update state when the flag value actually changed to avoid
25+
// unnecessary re-renders during polling.
26+
setState((prev) => {
27+
if (prev && prev.flagKey === data.flagKey && prev.flagValue === data.flagValue) {
28+
return prev;
29+
}
30+
return { flagKey: data.flagKey, flagValue: data.flagValue };
31+
});
32+
setError(null);
33+
} catch (e) {
34+
setError(`Failed to fetch flag: ${e instanceof Error ? e.message : String(e)}`);
35+
}
36+
}, []);
37+
38+
useEffect(() => {
39+
fetchFlag();
40+
41+
// Poll every 2 seconds to pick up flag changes.
42+
const interval = setInterval(fetchFlag, 2000);
43+
return () => clearInterval(interval);
44+
}, [fetchFlag]);
45+
46+
if (error) {
47+
return (
48+
<div className="app app--off">
49+
<p>{error}</p>
50+
</div>
51+
);
52+
}
53+
54+
if (!state) {
55+
return (
56+
<div className="app app--off">
57+
<p>Initializing...</p>
58+
</div>
59+
);
60+
}
61+
62+
return (
63+
<div className={`app ${state.flagValue ? 'app--on' : 'app--off'}`}>
64+
<p>
65+
The {state.flagKey} feature flag evaluates to {String(state.flagValue)}.
66+
</p>
67+
</div>
68+
);
69+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Mock Edge Config server for local development without a Vercel account.
3+
*
4+
* The @vercel/edge-config SDK supports external connection strings. When
5+
* EDGE_CONFIG points to this local server, the SDK reads flag data from here
6+
* instead of edge-config.vercel.com.
7+
*
8+
* Usage: set EDGE_CONFIG to
9+
* http://localhost:3000/api/edge-config-mock/ecfg_mock?token=mock&id=ecfg_mock
10+
*/
11+
12+
import { type NextRequest } from 'next/server';
13+
14+
// A minimal LaunchDarkly flag payload. The Vercel SDK's EdgeFeatureStore
15+
// expects this shape when it calls edgeConfig.get("LD-Env-{clientSideId}").
16+
const MOCK_FLAG_PAYLOAD = {
17+
flags: {
18+
'sample-feature': {
19+
key: 'sample-feature',
20+
on: true,
21+
prerequisites: [],
22+
targets: [],
23+
rules: [],
24+
fallthrough: { variation: 0 },
25+
offVariation: 1,
26+
variations: [true, false],
27+
clientSideAvailability: { usingMobileKey: true, usingEnvironmentId: true },
28+
clientSide: true,
29+
salt: 'mock-salt',
30+
trackEvents: false,
31+
trackEventsFallthrough: false,
32+
version: 1,
33+
deleted: false,
34+
},
35+
},
36+
segments: {},
37+
};
38+
39+
export async function GET(
40+
_request: NextRequest,
41+
{ params }: { params: Promise<{ path: string[] }> },
42+
) {
43+
const { path } = await params;
44+
45+
// The SDK calls paths like /{edgeConfigId}/item/{key} and /{edgeConfigId}/items.
46+
// Skip the first segment (the Edge Config ID) and match on the rest.
47+
const subpath = path.slice(1);
48+
49+
// GET /{id}/item/{key} — read a single Edge Config item.
50+
if (subpath[0] === 'item' && subpath.length >= 2) {
51+
return Response.json(MOCK_FLAG_PAYLOAD);
52+
}
53+
54+
// GET /{id}/items — list all items as a key-value object. The SDK's
55+
// development cache reads all items and looks up keys via items[key].
56+
if (subpath[0] === 'items') {
57+
const clientSideId = process.env.LD_CLIENT_SIDE_ID || 'mock-client-side-id';
58+
return Response.json({ [`LD-Env-${clientSideId}`]: MOCK_FLAG_PAYLOAD });
59+
}
60+
61+
// GET /{id}/digest — return a hash (used for change detection).
62+
if (subpath[0] === 'digest') {
63+
return Response.json('mock-digest');
64+
}
65+
66+
return Response.json({ error: 'Not found' }, { status: 404 });
67+
}

0 commit comments

Comments
 (0)