Skip to main content

Feature Flag System Architecture

Overview

The Next.js monorepo implements a feature flag system that enables controlled feature rollouts, A/B testing, and runtime configuration management across all applications. The system integrates LaunchDarkly as the primary feature flag provider with Vercel Edge Config for enhanced performance and caching.

System Architecture

The feature flag system architecture illustrates three interconnected workflows that enable dynamic feature control across the Charles Schwab applications. The Feature Flag Flow diagram below shows the runtime request lifecycle, where client requests pass through Next.js middleware and the withFlags middleware for flag precomputation, evaluation via LaunchDarkly SDK and Vercel Edge Config, ultimately resulting in URL rewriting and application responses.

The Flag Management workflow demonstrates how flags are configured through the LaunchDarkly dashboard, synchronized with environment variables and Edge Config, and evaluated at runtime. The Development Workflow ensures type safety and code quality through flag declaration, TypeScript type generation, registration, code generation, and static analysis.

This integrated approach provides both runtime flexibility and development-time safety, enabling teams to deploy features confidently with fine-grained control over their exposure to users.

Core Components

The feature flag system is built on three foundational components that work together to provide a type-safe, performant, and maintainable flag management system.

1. Flag Declaration System

Location: packages/utilities/src/flags/index.ts

The flag declaration system provides a centralized factory pattern that ensures consistency across all feature flags in the monorepo. It abstracts LaunchDarkly adapter configuration and identity resolution, allowing developers to focus on flag logic rather than integration details.

Key Responsibilities:

Architecture Pattern:

ComponentPurposeBenefit
Adapter FactoryCreates LaunchDarkly adapters with environment configSingle source of truth for SDK configuration
Identity FunctionResolves user context from cookies/headersConsistent user targeting across all flags
Type WrapperWraps flags with TypeScript genericsCompile-time type safety for flag values
Default HandlerManages fallback values when evaluation failsGraceful degradation

Integration Points:

  • Environment Variables: LAUNCHDARKLY_PROJECT_KEY, LAUNCHDARKLY_CLIENTSIDE_ID, EDGE_CONFIG_FLAGS
  • SDK Dependencies: @flags-sdk/launchdarkly, flags/next
  • Identity Resolution: Cookie and header-based user identification

2. Individual Flag Definition

Each feature flag is defined in its own TypeScript file following a standardized template pattern.

Flag File Structure:

ElementPurposeExample
Flag KeyUnique identifier matching LaunchDarkly'featureCheckBotIdModes'
Default ValueFallback when LaunchDarkly unavailablefalse, 'off', 0
DescriptionHuman-readable purpose'Feature: Bot detection modes'
Type ParameterTypeScript type for flag value<boolean>, <string>, <number>

Naming Conventions:

  • File Name: camelCase.ts (e.g., featureCheckBotIdModes.ts)
  • Variable Name: Same as file name (e.g., featureCheckBotIdModes)
  • Export: Default export + named export for flexibility

Location Pattern: apps/<app-name>/src/flags/flags/<flagName>.ts

3. Flag Aggregation and Export

The aggregation layer automatically discovers and exports all flags using Node.js require.context(), eliminating manual registration and reducing human error.

Discovery Architecture:

Aggregation Benefits:

BenefitDescriptionImpact
Zero ConfigurationFlags automatically discoveredNo manual imports needed
Type InferenceTypeScript infers complete flag unionFull IntelliSense support
Build ValidationMissing flags caught at build timePrevents runtime errors
ScalabilityAdd flags without touching indexReduces merge conflicts

Export Pattern: apps/<app-name>/src/flags/index.ts

Generated Artifacts:

  • Flag Array: All flags for the application
  • Type Definitions: Union types for flag keys
  • Permutation Input: Used by generatePermutations() for static variants

Middleware Integration

withFlags Middleware

The withFlags middleware sits in the Next.js middleware chain and handles flag precomputation and URL rewriting. This enables static generation of pages with different flag combinations while maintaining a clean URL structure.

Middleware Flow:

Middleware Responsibilities:

ResponsibilityImplementationPurpose
Flag Precomputationprecompute(allFlags)Generate unique code for flag combination
URL Rewriting/${code}${pathname}Route to correct static variant
Context PassingReturn { code, newUrl }Pass flag context to application
PerformanceEdge-compatible codeFast execution at CDN edge

URL Structure:

Original:    /education/articles/investment-basics
Rewritten: /abc123/education/articles/investment-basics
^^^^^^^ flag permutation code

Integration Points:

  • Input: Next.js NextRequest object
  • Output: Permutation code and rewritten URL
  • Dependencies: flags/next SDK, allFlags export
  • Execution: Runs on every request at edge locations

Flag Evaluation Methods

The system provides two distinct evaluation methods optimized for different rendering strategies in Next.js.

1. Static Flag Evaluation

Use Case: Server-side rendering (SSR), static site generation (SSG), and incremental static regeneration (ISR)

Architectural Pattern:

Method Characteristics:

AspectDescriptionBenefit
Evaluation TimeBuild time or during SSR/ISREliminates runtime evaluation overhead
Cache StrategyCode-based lookup in precomputed mapO(1) lookup performance
Type SafetyGeneric parameter <T> for type inferenceCompile-time type checking
FallbackReturns undefined if flag not foundSafe degradation

Parameters:

  • flags: Complete flag array from app
  • code: Permutation code from middleware
  • flagKey: String identifier for specific flag

Return: Promise<T | undefined> where T is flag value type

2. Dynamic Flag Evaluation

Use Case: API routes, server actions, runtime-evaluated features, client-side features

Architectural Pattern:

Method Characteristics:

AspectDescriptionBenefit
Evaluation TimeRequest timeReal-time flag changes without rebuild
LaunchDarkly QueryDirect SDK call per evaluationAlways current flag state
User TargetingEvaluates per-user contextPersonalized experiences
PerformanceCached by Edge ConfigFast evaluation at edge

Parameters:

  • flags: Complete flag array from app
  • flagKey: String identifier for specific flag

Return: Promise<T | null> where T is flag value type

Comparison Matrix:

CriteriaStatic EvaluationDynamic Evaluation
SpeedFastest (O(1) lookup)Fast (cached SDK call)
FreshnessBuild-time snapshotReal-time values
User TargetingNot applicableFull targeting support
Use CasesSSG, ISR, SSR pagesAPI routes, server actions
Cache StrategyPrecomputed permutationsEdge Config + LaunchDarkly

LaunchDarkly Integration

The system integrates with LaunchDarkly through two complementary approaches, balancing performance with functionality.

1. Server-Side SDK Integration

Architecture Overview:

SDK Integration Pattern:

ComponentPurposeImplementation
Singleton ClientSingle SDK instance per processPrevents multiple connections
Lazy InitializationCreate client on first useFaster cold starts
Connection PoolingReuse streaming connectionReduced API calls
Graceful DegradationDefault values on failureApplication resilience

Connection Lifecycle:

  1. Initialization: Create client with SDK key
  2. Handshake: Connect to LaunchDarkly streaming API
  3. Configuration Download: Receive all flag definitions
  4. Ready State: Accept evaluation requests
  5. Updates: Receive real-time flag changes via stream

Environment Configuration:

  • LAUNCHDARKLY_SDK_KEY: Server-side SDK authentication key
  • LAUNCHDARKLY_PROJECT_KEY: Project identifier
  • LAUNCHDARKLY_ENVIRONMENT: Environment (production, staging, etc.)

2. Vercel Edge Config Integration

Architecture Overview:

Edge Config Benefits:

BenefitDescriptionImpact
CDN DistributionFlags cached globally at edge locationsUltra-low latency (<10ms)
Reduced API CallsLaunchDarkly queried only on cache missLower costs, better performance
Automatic SyncVercel syncs flags from LaunchDarklyNo manual cache management
Fallback SupportSDK gracefully falls back to LaunchDarklyHigh availability

Sync Process:

  1. Flag Change: Developer updates flag in LaunchDarkly
  2. Webhook Trigger: LaunchDarkly notifies Vercel
  3. Edge Config Update: Vercel updates distributed cache
  4. Propagation: Changes propagate to all edge locations
  5. Application Use: Next.js apps use updated flags

Configuration:

  • EDGE_CONFIG_FLAGS: Connection string for Edge Config store
  • LAUNCHDARKLY_CLIENTSIDE_ID: Client-side ID for Edge SDK

Performance Comparison:

MethodLatencyCostFreshness
Direct LaunchDarkly50-200msHighReal-time
Edge Config1-10msLowNear real-time (~1 min delay)
Hybrid (Recommended)1-10msLowNear real-time

User Context and Targeting

Identity Resolution

The identity resolution system determines user context for personalized flag targeting, balancing user identification with privacy concerns.

Identity Resolution Flow:

Identity Sources:

SourcePriorityPurposeFormat
Header1 (Highest)Server-to-server identificationx-schwab-external-id
Cookie2Browser-based identificationschwab-external-id
Anonymous3 (Fallback)No user identificationEmpty string ''

Context Structure:

interface LDContext {
key: string; // User identifier
anonymous?: boolean; // Whether user is identified
custom?: { // Optional custom attributes
[key: string]: any;
};
}

Targeting Capabilities:

Deduplication Strategy:

The dedupe() wrapper ensures identity resolution happens only once per request, even if multiple flags are evaluated:

  • First Call: Resolves identity from headers/cookies
  • Subsequent Calls: Returns cached context
  • Cache Scope: Per-request (not shared across requests)
  • Benefits: Reduced overhead, consistent context

Privacy Considerations:

  • Minimal PII: Only external ID used (no names, emails, etc.)
  • Anonymous Support: Graceful handling of unauthenticated users
  • Secure Transmission: All context data encrypted in transit
  • Compliance: Aligns with privacy policies

Environment Configuration

Required Environment Variables

Feature Flag Environment Variables
# LaunchDarkly Configuration
LAUNCHDARKLY_SDK_KEY=sdk-xxxxx-xxxxx-xxxxx
LAUNCHDARKLY_API_KEY=api-xxxxx-xxxxx-xxxxx
LAUNCHDARKLY_PROJECT_KEY=project-key
LAUNCHDARKLY_ENVIRONMENT=production
LAUNCHDARKLY_CLIENTSIDE_ID=clientside-id

# Vercel Edge Config
EDGE_CONFIG_FLAGS=https://edge-config.vercel.com/xxxxx

# Security
FLAGS_SECRET=secure-secret-key

Application-Specific Implementations

1. www.schwab.com

  • Flag Count: 20+ feature flags
  • Key Features: Bot detection, content variations, A/B testing
  • Integration: Full LaunchDarkly integration with Edge Config caching

2. client.schwab.com

  • Flag Count: 15+ feature flags
  • Key Features: Client portal features, UI variations
  • Integration: Dynamic flag evaluation with client context

3. meganav-mfe

  • Flag Count: 10+ feature flags
  • Key Features: Navigation features, responsive design toggles
  • Integration: Micro-frontend flag synchronization

Testing and Mocking

Test Utilities

Feature Flag Testing
// packages/test/src/mock/base/Mockery.ts
export class Mockery {
static mockEvaluateStaticFlag(flags: Record<string, boolean> | undefined): void {
jest.doMock('@schwab/utilities/flags', () => ({
evaluateStaticFlag: () => Promise.resolve(flags),
}));
}

static mockEvaluateDynamicFlag(flags: Record<string, boolean> | undefined): void {
jest.doMock('@schwab/utilities/flags', () => ({
evaluateDynamicFlag: () => Promise.resolve(flags),
}));
}
}

Performance Optimization

1. Flag Precomputation

  • Strategy: Precompute flag combinations at build time
  • Benefit: Reduces runtime evaluation overhead
  • Implementation: generatePermutations() function creates static variants

2. Edge Config Caching

  • Strategy: Cache flag configurations at CDN edge locations
  • Benefit: Reduces LaunchDarkly API calls and latency
  • Implementation: Vercel Edge Config with LaunchDarkly sync

3. Static Site Generation

  • Strategy: Generate static pages for each flag permutation
  • Benefit: Eliminates runtime flag evaluation for static content
  • Implementation: URL rewriting with flag codes

Monitoring and Analytics

1. Flag Performance Tracking

  • Metrics: Flag evaluation latency, cache hit rates
  • Tools: LaunchDarkly analytics, Vercel analytics
  • Alerts: Flag evaluation errors, timeout thresholds

2. A/B Test Analytics

  • Integration: Tealium analytics for flag-based experiments
  • Tracking: Conversion rates, user engagement metrics
  • Reporting: LaunchDarkly experimentation dashboard

Development Workflow

1. Creating New Flags

Flag Creation Process
# 1. Create flag file using template
cp packages/utilities/src/flags/_template.txt apps/app-name/src/flags/flags/newFlag.ts

# 2. Update flag declaration
# Edit the new flag file with proper key, defaultValue, description

# 3. Flag is automatically discovered via require.context
# No manual registration required

2. Flag Lifecycle Management

  1. Development: Create flag with defaultValue: false
  2. Testing: Enable for specific user segments
  3. Rollout: Gradually increase percentage
  4. Cleanup: Remove flag code after full rollout

3. Code Generation

  • Automatic Discovery: Flags are discovered via require.context()
  • Type Safety: TypeScript interfaces ensure type-safe flag usage
  • Build Integration: Flags are validated at build time

Security Considerations

1. Access Control

  • LaunchDarkly RBAC: Role-based access control for flag management
  • Environment Separation: Strict environment isolation
  • API Key Rotation: Regular rotation of LaunchDarkly API keys

2. Data Protection

  • User Context: Minimal PII in flag targeting context
  • Encryption: All flag data encrypted in transit and at rest
  • Audit Logging: Complete audit trail for flag changes

Dependencies

Core Dependencies

PackageVersionPurpose
@flags-sdk/launchdarkly^0.3.2LaunchDarkly integration SDK
@launchdarkly/vercel-server-sdk^1.3.34Vercel-optimized LaunchDarkly SDK
@launchdarkly/node-server-sdk^9.10.2Node.js LaunchDarkly SDK
@vercel/edge-configLatestVercel Edge Config client
flags^4.0.1Core flag management library

Development Dependencies

  • Testing: Jest mocking utilities for flag simulation
  • TypeScript: Type definitions for flag declarations
  • Build Tools: Flag validation and code generation tools