Edge-Side A/B Testing: Routing Traffic Between Vercel and Cloudflare Workers
Master edge-side A/B testing by routing traffic between Vercel and Cloudflare Workers. Learn how to perform traffic splitting for seamless canary releases.
When we needed to validate a major architectural change for our frontend, we couldn't afford the risk of a full-scale deployment. We wanted to run an experiment: route 20% of our production traffic to a new Vercel-hosted version while keeping the rest on our stable Cloudflare-hosted build. Implementing edge-side A/B testing this way allowed us to measure performance metrics in real-world scenarios without impacting our entire user base.
The Problem with Client-Side Redirects
Initially, we tried using client-side JavaScript to fetch different versions of the app. It was a disaster. The "flicker" effect was noticeable, and our SEO rankings took a small hit because search engine crawlers were getting confused by the dynamic content injection. We needed something faster, something that happened before the request even reached our origin servers. That’s where Cloudflare Workers shine as a global traffic router.
Architecting the Edge Router
Since we were already using Deploying Next.js 16 to Cloudflare Workers with OpenNext, we decided to leverage that infrastructure. By placing a Cloudflare Worker in front of both our Vercel deployment and our existing Cloudflare-hosted site, we could inspect incoming requests and route them based on a cookie or a random hash.
Here is how the request flow looks:
Flow diagram: User → Cloudflare Worker; Cloudflare Worker → Route A Vercel Edge Functions; Cloudflare Worker → Route B Cloudflare Workers Site; Vercel Edge Functions → Response; Cloudflare Workers Site → Response
Implementing Traffic Splitting
The logic itself is straightforward. We use a simple cookie-based persistence strategy to ensure that once a user is bucketed into a specific experiment group, they stay there. If no cookie exists, we assign them to a bucket using a random number generator.
Here is a snippet of the routing logic we implemented in our index.js worker file:
JAVASCRIPTexport default { async fetch(request, env) { const cookie = request.headers.get(CE9178">'cookie') || CE9178">''; let bucket = cookie.includes(CE9178">'exp_group=B') ? CE9178">'B' : null; if (!bucket) { // 20% traffic to Vercel (Group B), 80% to Cloudflare (Group A) bucket = Math.random() < 0.2 ? CE9178">'B' : CE9178">'A'; } const url = new URL(request.url); if (bucket === CE9178">'B') { url.hostname = CE9178">'vercel-app.example.com'; } else { url.hostname = CE9178">'cloudflare-app.example.com'; } const newRequest = new Request(url, request); const response = await fetch(newRequest); // Persist the group assignment const newResponse = new Response(response.body, response); newResponse.headers.append(CE9178">'Set-Cookie', CE9178">`exp_group=${bucket}; Path=/; Max-Age=3600`); return newResponse; } };
Comparing Deployment Targets
When you're routing traffic between two different platforms, you have to account for environment parity. We found that Next.js Traffic Shadowing: Architecting Canary Releases at the Edge provided a good baseline, but managing two separate build pipelines requires constant vigilance.
| Feature | Vercel Edge Functions | Cloudflare Workers |
|---|---|---|
| Global Latency | Excellent | Exceptional |
| Cold Starts | Minimal | Zero |
| Binding Support | Vercel KV/Blob | KV/D1/R2 |
| Ease of Routing | High (via Middleware) | High (via Worker) |
Lessons Learned
We spent about two days debugging an issue where the Vercel-hosted version was failing to read cookies set by the Cloudflare Worker. It turned out to be a header casing issue—Vercel's edge runtime was expecting specific header formats that our worker wasn't consistently providing.
If I were to do this again, I’d invest more time in Next.js App Router Server-Side Prefetching: Background Cache Warming to ensure that the Vercel-hosted experiment feels just as snappy as the Cloudflare-hosted one. Running edge-side A/B testing successfully isn't just about the routing logic; it's about making sure your edge deployment strategies are perfectly aligned across both providers.
FAQ
Does this routing add significant latency? In our testing, the worker overhead was roughly 15-20ms, which is negligible compared to the benefits of true server-side splitting.
How do I handle session state across platforms? This is the hardest part. We used a shared Redis instance to ensure that session data remains consistent regardless of whether the user hits Vercel or Cloudflare.
Can I use this for more than two versions?
Yes, you can extend the Math.random() logic to split traffic into as many buckets as you need, though keeping track of the user experience becomes exponentially more difficult.
Edge-side A/B testing has fundamentally changed how we ship features. By decoupling the routing layer from our application logic, we’ve gained the ability to test risky changes in production with total confidence.