Implementing A/B Testing at the CDN Edge
Introduction to Edge-Side Experimentation
Modern product development relies heavily on continuous experimentation. To optimize conversion rates, user engagement, and revenue, product teams must constantly run A/B and multivariate tests. Historically, these tests have been implemented in one of two ways: client-side or server-side.
Both approaches present significant drawbacks. Client-side testing frameworks inject render-blocking JavaScript files that modify the DOM after the page loads, leading to the dreaded "flash of original content" (FOOC) and severely hurting Core Web Vitals like Cumulative Layout Shift (CLS) and Largest Contentful Paint (LCP). Server-side testing avoids layout shifts but bypasses CDN caching completely, forcing every request back to the origin database and significantly increasing global response times.
Edge-side experimentation offers a modern solution that combines the best of both worlds. By running lightweight code at the CDN edge—using technologies like Cloudflare Workers, AWS CloudFront Functions, or Fastly Compute—developers can intercept requests, assign experimentation buckets, and serve localized variants in sub-milliseconds.
Because the processing occurs at the edge node closest to the user, pages are delivered pre-rendered and fully optimized, eliminating client-side layout shifts without sacrificing CDN cache efficiency. This article details the architecture, request lifecycle, caching strategies, and code required to deploy A/B testing at the edge.
The Edge Experimentation Lifecycle
Executing an A/B test at the edge requires intercepting the HTTP request before it reaches the cache or origin server. The lifecycle of an edge-level experiment follows these precise stages:
- Request Interception: The edge worker intercepts the user's incoming HTTP request.
- Bucket Evaluation: The worker checks the request's cookies for an existing experiment bucket identifier (e.g.,
exp_bucket=variant_b). If no cookie is present, the worker assigns the user to a bucket randomly or based on targeting criteria (such as geographic location, device type, or referral source). - Routing & Rewriting: The worker modifies the request path or headers. For example, if a user in
variant_brequests/homepage, the worker rewrites the request internally to fetch/homepage-variant-b. Alternatively, it can stream the HTML response and rewrite elements on the fly using a streaming parser. - Cache Key Partitioning: The worker checks the CDN cache using a modified cache key that includes the bucket ID. This ensures users in different buckets do not receive cached content from other variants.
- Response Injection & Cookie Setting: When returning the response, the worker appends a
Set-Cookieheader to persist the bucket assignment for subsequent page loads and attaches custom headers to notify client-side analytics tools.
Edge Implementation Code: Cloudflare Worker Example
To illustrate this pattern, let's look at a practical Cloudflare Worker implementation that handles cookie validation, random traffic splitting (50/50), request rewriting, and cookie persistence:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
# Only run experiments on the root landing page
if (url.pathname !== '/') {
return fetch(request);
}
const cookieHeader = request.headers.get('Cookie') || '';
let bucket = '';
# Check for existing experiment cookie
if (cookieHeader.includes('ab_bucket=control')) {
bucket = 'control';
} else if (cookieHeader.includes('ab_bucket=variant_b')) {
bucket = 'variant_b';
}
# If no bucket exists, assign one randomly
if (!bucket) {
bucket = Math.random() < 0.5 ? 'control' : 'variant_b';
}
# Rewrite request URL internally based on the assigned bucket
let targetUrl = url.toString();
if (bucket === 'variant_b') {
url.pathname = '/variant-b';
targetUrl = url.toString();
}
# Fetch the content from the rewritten URL
let response = await fetch(targetUrl, {
headers: request.headers
});
# Clone the response to modify headers
let newResponse = new Response(response.body, response);
# Set the cookie so the user remains in the same bucket
newResponse.headers.append(
'Set-Cookie',
`ab_bucket=${bucket}; Path=/; Max-Age=2592000; Secure; SameSite=Lax`
);
# Expose the bucket in a header for client-side analytics
newResponse.headers.set('X-Experiment-Bucket', bucket);
return newResponse;
}
In this architecture, the web browser receives the response HTML immediately without any client-side JavaScript execution required to show the variant. LCP and CLS remain completely unaffected by the experiment.
CDN Caching and Cache-Key Overrides
A common pitfall with edge A/B testing is cache pollution. If your CDN caches the response for /, the first user's variant (e.g., Control) might get stored in the public cache, causing all subsequent users to see the Control variant regardless of their assigned bucket. To avoid this, you must partition the cache.
Modern CDNs allow you to override the default cache key. A cache key is the unique identifier the CDN uses to look up cached files.
By incorporating the experiment bucket ID directly into the cache key (e.g., url + bucket), you create separate cache pools for each experiment variant. This ensures that users in the Control bucket receive cached Control content, and users in Variant B receive cached Variant B content, maximizing your cache hit ratio while preserving the integrity of the test.
Optimizing Edge Experimentation with Bramsley
Deploying feature experiments at the CDN edge requires precise cache-key overrides and routing logic to avoid cache pollution and high origin load. Bramsley Digital Studio specializes in engineering high-performance edge experimentation architectures.
How Bramsley Streamlines Edge A/B Testing
We help product teams configure multi-variant routing and dynamic HTML rewrites at the network edge with zero performance overhead:
- Dynamic Cache Partitioning: Custom cache keys ensure users receive correct variants without polluting the public CDN cache.
- Flicker-Free Delivery: Edge-side DOM modification eliminates layout shifts (CLS) and improves page-load speed.
- Real-Time Analytics Ingestion: Sync edge telemetry with Segment or Google Analytics directly from edge workers.
Partner with our edge engineering team to unlock fast, high-performance experimentation. Get in touch with Bramsley today.