Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ NEXT_PUBLIC_E2B_DOMAIN=e2b.dev
# (Required if NEXT_PUBLIC_INCLUDE_REPORT_ISSUE=1)
# PLAIN_API_KEY=

### LaunchDarkly feature flags
### Use the SDK key for the LaunchDarkly environment selected below.
# LAUNCHDARKLY_SDK_KEY=
# FEATURE_FLAG_ENVIRONMENT=staging

### OTEL Configuration
# OTEL_SERVICE_NAME=
# OTEL_EXPORTER_OTLP_ENDPOINT=
Expand All @@ -74,7 +79,7 @@ NEXT_PUBLIC_E2B_DOMAIN=e2b.dev
### OPTIONAL CLIENT ENVIRONMENT VARIABLES
### =================================

### PostHog analytics and feature flag project key
### PostHog analytics project key
# NEXT_PUBLIC_POSTHOG_KEY=

### PostHog source map upload (build-time only, used by @posthog/nextjs-config).
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ We use a layered testing strategy with Vitest and Playwright. For details on tes
### Environment Variables
See [`src/lib/env.ts`](./src/lib/env.ts) for all required environment variables and their validation schemas.

### Feature Flags
Feature flags are evaluated server-side with LaunchDarkly via OpenFeature and hydrated into the dashboard client. To add a flag:

1. Create it in LaunchDarkly `staging` and `production` with the same key.
2. Add it to [`src/core/modules/feature-flags/definitions.ts`](./src/core/modules/feature-flags/definitions.ts) with a safe default value.
3. Use `featureFlags.isEnabled(...)` on the server or `useFeatureFlag(...)` inside dashboard client components.
4. Target users or teams in LaunchDarkly using the `user` and `team` contexts.

Set `LAUNCHDARKLY_SDK_KEY` and `FEATURE_FLAG_ENVIRONMENT=staging|production` for environments that should use LaunchDarkly.

## Production Deployment

This application is optimized for deployment on Vercel:
Expand Down
32 changes: 24 additions & 8 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@launchdarkly/node-server-sdk": "^9.11.2",
"@launchdarkly/openfeature-node-server": "^1.2.0",
"@next/env": "^16.2.7",
"@openfeature/server-sdk": "^1.22.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.77.0",
"@opentelemetry/exporter-logs-otlp-http": "^0.203.0",
Expand Down Expand Up @@ -117,7 +120,6 @@
"pathe": "^2.0.3",
"pino": "^9.7.0",
"posthog-js": "^1.268.1",
"posthog-node": "^5.38.0",
"react": "19.2.4",
"react-day-picker": "^9.9.0",
"react-dom": "19.2.4",
Expand Down
14 changes: 14 additions & 0 deletions scripts/check-app-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,19 @@ const schema = appEnvSchema
path: ['PLAIN_API_KEY'],
}
)
.refine(
(data) => {
if (data.LAUNCHDARKLY_SDK_KEY) {
return !!data.FEATURE_FLAG_ENVIRONMENT
}

return true
},
{
message:
'LAUNCHDARKLY_SDK_KEY is set, but FEATURE_FLAG_ENVIRONMENT is missing',
path: ['FEATURE_FLAG_ENVIRONMENT'],
}
)

validateEnv(schema)
14 changes: 9 additions & 5 deletions src/core/modules/feature-flags/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ export type FeatureFlagContext = {
slug?: string
name?: string
}
environment?: 'production' | 'preview' | 'development'
environment?: 'production' | 'staging'
}

export function getFeatureFlagEnvironment(): FeatureFlagContext['environment'] {
switch (process.env.FEATURE_FLAG_ENVIRONMENT) {
case 'production':
case 'staging':
return process.env.FEATURE_FLAG_ENVIRONMENT
}

switch (process.env.VERCEL_ENV) {
case 'production':
case 'preview':
case 'development':
return process.env.VERCEL_ENV
return 'production'
default:
return 'development'
return 'staging'
}
}
16 changes: 8 additions & 8 deletions src/core/modules/feature-flags/feature-flags.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import type {
PayloadFeatureFlagDefinition,
} from '@/core/modules/feature-flags/types'
import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger'
import {
type FeatureFlagProvider,
type FeatureFlagSnapshot,
postHogFeatureFlagProvider,
} from './posthog-provider.server'
import { launchDarklyOpenFeatureProvider } from './launchdarkly-openfeature-provider.server'
import type {
FeatureFlagProvider,
FeatureFlagSnapshot,
} from './provider.server'

export type FeatureFlagService = {
isEnabled(
Expand Down Expand Up @@ -89,12 +89,12 @@ function getEvaluatedValue(
}

export function createFeatureFlagService(
provider: FeatureFlagProvider = postHogFeatureFlagProvider
provider: FeatureFlagProvider = launchDarklyOpenFeatureProvider
): FeatureFlagService {
return {
async isEnabled(flagId, context) {
const flag = FEATURE_FLAGS[flagId]
const snapshot = await provider.evaluate(context, [flag.key])
const snapshot = await provider.evaluate(context, [flag])
const value = snapshot.getFlagValue(flag.key)

if (typeof value === 'boolean') {
Expand All @@ -112,7 +112,7 @@ export function createFeatureFlagService(
][]
const snapshot = await provider.evaluate(
context,
flags.map(([, flag]) => flag.key)
flags.map(([, flag]) => flag)
)

return flags.map(([id, flag]) => ({
Expand Down
Loading
Loading