Feature Flags at the Edge: CDN-Based Experimentation and Progressive Rollouts
Feature flags have become essential infrastructure for modern software delivery. They decouple deployment from release, allowing teams to push code to production without immediately exposing it to users. Traditionally, feature flags evaluate on the server or in the client browser. Moving flag evaluation to CDN edge nodes eliminates the latency penalty of client-side evaluation and the origin dependency of server-side evaluation, creating a third option that combines the best properties of both approaches. This article explores the architecture and implementation patterns for edge-based feature flagging on top of edge function platforms.
Why Evaluate Feature Flags at the Edge
Client-side feature flags add visible delay. The browser loads the page, fetches the feature flag configuration from a flag service, evaluates which variant to show, and then renders the correct version. Users see a flash of the default content before the flagged content appears. This layout shift harms Core Web Vitals CLS scores and creates a jarring experience.
Server-side feature flags avoid the flash but route every request through the origin server for flag evaluation. This negates the latency benefit of CDN caching because the response cannot be cached (the correct variant depends on user-specific flag evaluation). Every request pays the full origin round-trip cost.
Edge evaluation solves both problems. The CDN edge function evaluates the feature flag, determines the correct variant, and serves the appropriate response — all within milliseconds and without contacting the origin server. The response the user receives is already personalized to their variant, so there is no layout shift. And because each variant is a cacheable response (keyed by variant assignment), the CDN can cache variant-specific responses at each PoP for subsequent users in the same variant group.
Architecture: Flag Configuration at the Edge
Edge feature flags require the flag configuration (which flags exist, what percentage each variant receives, any targeting rules) to be available at every CDN PoP without making a remote call on each request. The standard architecture stores flag configuration in the CDN’s edge storage system and updates it asynchronously when configurations change.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
// Flag configuration stored in CDN edge KV store
// Updated asynchronously when admin changes flags
{
"flags": {
"new-checkout-flow": {
"enabled": true,
"variants": [
{ "name": "control", "weight": 70 },
{ "name": "variant-a", "weight": 20 },
{ "name": "variant-b", "weight": 10 }
],
"targeting": {
"countries": ["US", "CA", "GB"],
"exclude_bots": true
},
"sticky": true
},
"dark-mode-default": {
"enabled": false,
"variants": [
{ "name": "light", "weight": 100 },
{ "name": "dark", "weight": 0 }
],
"targeting": {},
"sticky": true
}
},
"version": "2026-10-01T08:30:00Z"
}
The flag configuration updates through a push mechanism. When an administrator changes a flag in the management interface, the change propagates to every CDN PoP through the edge storage replication system. Cloudflare KV replicates globally within 60 seconds. Vercel Edge Config propagates within seconds. Fastly Config Store updates are near-instant across all PoPs. This propagation delay is acceptable because feature flag changes are infrequent events, and a 60-second delay between flag change and global effect is within tolerance for most use cases.
Deterministic Variant Assignment
Consistent variant assignment is the most critical correctness requirement for edge feature flags. The same user must receive the same variant on every request, regardless of which CDN PoP serves the request. Without consistency, users experience jarring variant switches as they navigate between pages or as their requests route to different PoPs.
// Deterministic variant assignment using hash-based bucketing
function assignVariant(userId, flagName, variants) {
// Create a deterministic hash from user + flag combination
const input = `${userId}:${flagName}`;
const hash = cyrb53(input);
// Normalize hash to 0-9999 range for percentage-based assignment
const bucket = hash % 10000;
// Walk through variants, accumulating weight
let accumulated = 0;
for (const variant of variants) {
accumulated += variant.weight * 100; // weight is percentage
if (bucket < accumulated) {
return variant.name;
}
}
// Fallback to last variant (handles floating point edge cases)
return variants[variants.length - 1].name;
}
// Fast, non-cryptographic hash — deterministic across all PoPs
function cyrb53(str) {
let h1 = 0xdeadbeef;
let h2 = 0x41c6ce57;
for (let i = 0; i < str.length; i++) {
const ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}
The hash-based approach requires no shared state between PoPs. Each PoP computes the same hash for the same user-flag combination because the algorithm is deterministic. The user identifier typically comes from a first-party cookie. If no cookie exists (first visit), the edge function generates a random identifier, sets it as a cookie, and uses it for assignment. Subsequent requests include the cookie automatically.
A/B Testing at the Edge
A/B testing through the CDN extends the feature flag pattern with result tracking and statistical analysis. The edge function assigns variants and serves different content, but it also needs to record which user saw which variant so downstream analytics can measure conversion rates.
Exposure logging records which user was assigned to which variant. At the edge, this happens asynchronously using the waitUntil pattern so it does not add latency to the user response. The edge function returns the response immediately and sends the exposure event to an analytics endpoint in the background. This exposure data feeds into your analytics platform for conversion analysis and statistical significance calculations.
// Edge function — A/B test with exposure logging
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
if (url.pathname !== '/checkout') return fetch(request);
// Read flag configuration from edge KV
const flags = JSON.parse(await env.FLAGS.get('config'));
const flag = flags.flags['new-checkout-flow'];
if (!flag || !flag.enabled) {
return fetch(request);
}
// Get or create user identifier
const cookies = parseCookies(request.headers.get('Cookie'));
let userId = cookies['_uid'];
const isNewUser = !userId;
if (isNewUser) {
userId = crypto.randomUUID();
}
// Deterministic variant assignment
const variant = assignVariant(userId, 'new-checkout-flow', flag.variants);
// Fetch the correct variant page from origin or cache
const variantUrl = new URL(request.url);
variantUrl.searchParams.set('variant', variant);
const response = await fetch(variantUrl, {
cf: { cacheKey: `${url.pathname}:${variant}` },
});
// Clone response and add variant cookie
const newResponse = new Response(response.body, response);
if (isNewUser) {
newResponse.headers.append('Set-Cookie',
`_uid=${userId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=31536000`);
}
newResponse.headers.append('Set-Cookie',
`exp_checkout=${variant}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000`);
// Log exposure asynchronously — does not block response
ctx.waitUntil(logExposure(env, {
userId,
flag: 'new-checkout-flow',
variant,
timestamp: Date.now(),
country: request.cf?.country,
}));
return newResponse;
},
};
Progressive Rollouts and Canary Deployments
Feature flags at the edge enable progressive rollouts where a new feature starts at 1 percent traffic and incrementally increases to 100 percent based on monitoring data. The CDN evaluates the traffic percentage on every request, making changes effective globally within the edge storage propagation window (seconds to a minute).
Canary deployments extend this pattern to backend infrastructure. The edge function routes a configurable percentage of traffic to a new origin server (canary) while the majority continues to the stable origin. This uses the same deterministic hash-based assignment to ensure sticky routing: a user assigned to the canary continues hitting the canary on subsequent requests, preventing session inconsistencies.
// Edge-based canary deployment with progressive rollout
async function routeToOrigin(request, env) {
const flags = JSON.parse(await env.FLAGS.get('config'));
const canary = flags.flags['backend-v2-canary'];
if (!canary?.enabled) {
return env.STABLE_ORIGIN;
}
const cookies = parseCookies(request.headers.get('Cookie'));
const userId = cookies['_uid'] || request.headers.get('CF-Connecting-IP');
const variant = assignVariant(userId, 'backend-v2-canary', canary.variants);
return variant === 'canary' ? env.CANARY_ORIGIN : env.STABLE_ORIGIN;
}
The progressive rollout follows a defined schedule: 1 percent for 24 hours with error rate monitoring, then 5 percent for 24 hours, then 25 percent, then 50 percent, then 100 percent. At each stage, the team monitors error rates, latency percentiles, and business metrics through CDN analytics. If any metric degrades beyond threshold, the flag reverts to 0 percent canary traffic immediately. The entire rollout happens through flag configuration changes with no code deployments. For detailed deployment strategies, see CDN Blue-Green Deployment Strategies.
Cache Strategy for Flagged Content
Feature flags complicate CDN caching because the same URL can serve different content depending on the user’s variant assignment. Without careful cache key design, the CDN might serve Variant A content to a user assigned to Variant B. The solution is incorporating the variant assignment into the cache key.
There are two approaches to variant-aware caching. The first uses a custom cache key that includes the variant identifier. The edge function determines the variant, then constructs a cache key that appends the variant name. Cache lookups use this composite key, so each variant has its own cached response. The second approach uses the Vary header on a custom request header. The edge function adds a header like X-Variant: control to the request before passing it to the cache, and the origin responds with Vary: X-Variant to create separate cache entries per variant.
Both approaches increase cache storage by a factor equal to the number of active variants. A page with three variants triples its cache footprint. For high-traffic pages, this is negligible — the CDN has ample capacity. For long-tail pages with few requests, the additional variants reduce cache hit ratios because each variant gets fewer requests to stay warm. Monitor cache hit ratios per page during experiments and adjust TTLs or experiment targeting to maintain acceptable hit rates. See Cache Hit Ratio Optimization for strategies to manage cache fragmentation.
Targeting and Segmentation at the Edge
Edge feature flags support rich targeting rules that evaluate request-level attributes available at the CDN: geographic location (country, region, city from IP geolocation), device type (parsed from User-Agent), time of day (from the edge server clock), and custom attributes stored in cookies or request headers.
Geographic targeting is the most common edge-specific capability. It enables launching features in specific countries for regulatory compliance testing, rolling out performance optimizations in regions with high latency first, and running localized experiments that only make sense in certain markets. The CDN provides accurate geolocation data on every request, so targeting evaluation adds no additional latency or external dependencies.
Combining multiple targeting rules creates segments. A segment might be “mobile users in the United States visiting the checkout page during business hours.” The edge function evaluates all targeting criteria locally and applies the flag only when all conditions match. This granular control enables running experiments on precisely the user population that matters for the hypothesis being tested, reducing noise in experiment results and accelerating time to statistical significance.
Observability for Edge Experiments
Edge feature flags produce three categories of observability data: exposure events (who saw which variant), operational metrics (flag evaluation latency, error rates, cache impacts), and experiment outcomes (conversion rates per variant).
Exposure events are the foundation. Without accurate exposure data, experiment results are meaningless. The edge function must log every exposure reliably, even under high traffic. Use batched async logging: accumulate exposure events in memory during a request and flush them in a single batch using waitUntil. If the logging endpoint is unavailable, buffer events in edge storage for retry. Lost exposure events create statistical bias that invalidates experiment conclusions.
Operational metrics should track flag evaluation time (should be under 1 millisecond), edge storage read latency (typically 1 to 5 milliseconds for KV stores), variant distribution accuracy (actual traffic split should match configured weights within statistical bounds), and cookie set rates (indicating how many new users enter the experiment per time period). Integrate these metrics with your existing CDN monitoring infrastructure to create unified dashboards.