Vercel Edge vs Cloudflare Workers Performance Comparison

This guide benchmarks Vercel Edge Middleware against Cloudflare Workers for production request routing, measuring time to first byte (TTFB), cold start behavior, middleware execution overhead and failover latency with reproducible commands. The goal is to give you a decision you can defend with numbers rather than vendor marketing, and a diagnostic toolkit you can re-run against your own traffic.

Both platforms run on the V8 isolate model, so the differences are not about language or sandbox cost but about how each network instantiates, schedules and routes those isolates. By the end you will be able to attribute any latency you observe to a specific layer: DNS resolution, anycast routing, isolate warm-up or your own middleware logic.

Key objectives:

  • Measure and compare cold start latency and warm TTFB on both platforms with curl and wrk.
  • Separate network/DNS overhead from edge execution overhead using Server-Timing and time_namelookup.
  • Benchmark request transformation (header injection, rewrites, JWT checks) under load.
  • Build a vendor-neutral failover and load-balancing test so you can route between both providers.
Edge request path: Vercel Edge vs Cloudflare Workers Two parallel request paths from client through DNS and anycast routing into the edge isolate, annotated with the latency component each layer contributes. Where the milliseconds go Client dig + curl probe Cloudflare path Vercel path Anycast PoP (direct) time_connect Edge node (AWS-backed) extra hop in some regions V8 isolate (pre-warm) <10ms start V8 isolate (Edge Runtime) 50–150ms cold Middleware logic Server-Timing dur Middleware logic x-vercel-id trace Measured TTFB = sum of layers

The diagram above maps every latency contributor you will probe below. Read each comparison as: which layer changed, by how much, and whether it is under your control. Cold start and middleware cost are yours to tune; anycast density and peering are the platform’s.

Prerequisites & environment setup

You need two deployed endpoints serving an identical, trivial handler so the only variable is the platform. Provision a Cloudflare Worker with Wrangler and a Vercel Edge Middleware deployment, then point a subdomain at each. If you are new to either runtime, set up routing first with the Vercel Edge Middleware guide and validate matcher scoping before benchmarking, because an overly broad matcher inflates cold start payload and will distort your numbers.

# Tooling
npm install -g wrangler vercel
brew install wrk        # or apt-get install wrk
which dig curl          # both ship with most systems

# Deploy a control Worker
wrangler deploy ./worker/index.js --name edge-bench-cf
# Deploy the Vercel control
vercel deploy --prod ./vercel-bench

Expected output: Wrangler prints a *.workers.dev URL and Vercel prints a production URL. Map cf.example.com and vercel.example.com to them so DNS resolution is part of the test, mirroring real traffic. Keep DNS TTL low (60s) during the test window for quick re-pointing.

Step-by-step benchmark procedure

Step 1 — Establish a warm TTFB baseline

Fire a single request to each endpoint after a warm-up burst so isolates are already instantiated. This isolates steady-state latency from cold start noise.

# Warm both, then measure
for h in cf.example.com vercel.example.com; do
  curl -s -o /dev/null "https://$h/api/benchmark"   # warm-up
  curl -o /dev/null -s -w "$h => DNS:%{time_namelookup}s TCP:%{time_connect}s TLS:%{time_appconnect}s TTFB:%{time_starttransfer}s\n" \
    "https://$h/api/benchmark"
done

Expected output (illustrative, your region will differ):

cf.example.com => DNS:0.012s TCP:0.021s TLS:0.044s TTFB:0.061s
vercel.example.com => DNS:0.018s TCP:0.029s TLS:0.058s TTFB:0.089s

Subtract time_connect from time_starttransfer to get pure server-side time. If TTFB minus TLS is dominated by time_namelookup, your bottleneck is DNS, not the edge.

Step 2 — Measure cold start latency

Cold starts only appear when an isolate has been evicted. Force eviction by waiting (or deploying a fresh version) and probe the first hit. Cloudflare pre-initializes isolates across its network and typically returns sub-10ms starts; Vercel’s Edge Runtime forks a framework-aware runtime and may add 50–150ms on the first regional request.

# After a fresh deploy, hit each endpoint exactly once
curl -o /dev/null -s -w "COLD %{time_starttransfer}s\n" "https://cf.example.com/api/benchmark"
curl -o /dev/null -s -w "COLD %{time_starttransfer}s\n" "https://vercel.example.com/api/benchmark"

Expected output: the Cloudflare cold number sits close to its warm number; the Vercel cold number is visibly higher than its warm number. A steep first-request spike that disappears on request two confirms cold start, not network jitter.

Step 3 — Drive sustained load

Use wrk to push concurrent traffic and capture the latency distribution rather than a single sample. Tail percentiles (p99) reveal scheduling and backpressure behavior that averages hide.

wrk -t4 -c200 -d30s --latency https://cf.example.com/api/benchmark
wrk -t4 -c200 -d30s --latency https://vercel.example.com/api/benchmark

Expected output: a latency table with p50/p90/p99 and a requests/sec figure. Watch p99 — Cloudflare’s lower per-isolate memory overhead usually holds the tail flatter under burst, while Vercel’s build-time-compiled matchers reduce runtime evaluation cost but enlarge the cold payload.

Step 4 — Benchmark middleware transformation cost

Isolate the cost of your actual logic (header injection, rewrites, JWT validation) using Server-Timing. Both runtimes can emit it; instrument the handler to report its own duration.

curl -sI -H "X-Test-Header: benchmark" "https://cf.example.com/api/transform" | grep -i server-timing
curl -sI -H "X-Test-Header: benchmark" "https://vercel.example.com/api/transform" | grep -i server-timing

Expected output: server-timing: edge;dur=2.4 style lines. Compare the dur values across regions; high duration with low network time confirms the transformation logic, not the platform, is your bottleneck. For heavier rewrite work, see the patterns in A/B Testing with Vercel Edge Middleware, where matcher precision directly controls how often middleware executes.

Configuration reference

Cloudflare Worker with geo-targeted header injection

export default {
  async fetch(request, env, ctx) {
    const start = Date.now();
    const country = request.cf?.country || 'US';
    const originResponse = await fetch(request);
    const headers = new Headers(originResponse.headers);
    headers.set('X-Edge-Region', country);
    headers.set('Server-Timing', `edge;dur=${Date.now() - start}`);
    return new Response(originResponse.body, {
      status: originResponse.status,
      headers,
    });
  },
};

The request.cf object supplies geo metadata with no extra DNS lookup, so country-based routing costs effectively nothing. This is the ES module syntax required by Wrangler v3+.

Vercel Edge Middleware with conditional rewrite

import { NextRequest, NextResponse } from 'next/server';

export function middleware(req: NextRequest) {
  const start = Date.now();
  const { pathname } = req.nextUrl;
  let res = NextResponse.next();
  if (pathname.startsWith('/api/v2')) {
    res = NextResponse.rewrite(new URL('/api/v2', req.url));
  }
  res.headers.set('Server-Timing', `edge;dur=${Date.now() - start}`);
  return res;
}

export const config = { matcher: ['/api/:path*'] };

The matcher compiles routing rules at build time, cutting runtime evaluation but increasing the deployment artifact. Keep the matcher narrow so middleware does not run on static assets it never needs to touch. The same compile-time-versus-runtime trade-off appears when choosing runtimes for AWS, covered in Cloudflare Workers vs AWS Lambda@Edge for Request Routing.

Verification

Confirm you are measuring the edge and not a cache or origin. Inspect the platform trace headers and correlate them with timing.

curl -sI "https://cf.example.com/api/benchmark" | grep -Ei "cf-ray|cf-cache-status|server-timing"
curl -sI "https://vercel.example.com/api/benchmark" | grep -Ei "x-vercel-id|x-vercel-cache|server-timing"

Expected output: Cloudflare returns cf-ray and cf-cache-status: DYNAMIC (proving no cache hit); Vercel returns x-vercel-id with region and deployment metadata and x-vercel-cache: MISS. If you see HIT, your TTFB reflects a cached response, not isolate execution — bypass with a unique query string before benchmarking.

# DNS vs edge attribution
dig +trace cf.example.com @8.8.8.8 | tail -5
curl -o /dev/null -s -w "namelookup:%{time_namelookup}s ttfb:%{time_starttransfer}s\n" \
  "https://cf.example.com/api/benchmark?nocache=$RANDOM"

A low time_namelookup with the bulk of time in time_starttransfer confirms the edge runtime, not the resolver, owns the latency.

Troubleshooting

Cold start spikes during traffic bursts. Diagnosis: the first request after an idle window or fresh deploy shows 300–500ms while subsequent requests are fast. Fix: pre-warm with a scheduled ping (Cron Trigger on Cloudflare, a scheduled function or external monitor on Vercel) and route burst traffic through a CDN cache layer so most requests never reach a cold isolate. Roll back by lowering concurrency limits and buffering with a queue.

Geo-distributed users report inconsistent TTFB with identical payloads. Diagnosis: time_connect varies widely by region while time_starttransfer minus time_connect is stable. This is anycast/peering density, not your code. Fix: prefer the network with direct peering in your hot regions; Vercel’s AWS-backed nodes add a hop in some geographies. Lower DNS TTL to 60s so you can shift traffic quickly during regional degradation.

Server-Timing duration is high but origin is healthy. Diagnosis: synchronous string manipulation or unbuffered header parsing is causing backpressure inside the isolate. Fix: stream large bodies with ReadableStream instead of buffering, minimize per-request header parsing, and apply Cache-Control: public, s-maxage=3600 to offload repeated transformations to the cache.

Failover routes users to a degraded origin. Diagnosis: slow health checks plus DNS propagation lag leave traffic pointed at an unhealthy node. Fix: run edge-level health polling with sub-10s intervals and return HTTP 503 with Retry-After so clients back off while routing converges. Verify with a multi-region probe:

for h in eu.example.com us.example.com; do
  curl -s -o /dev/null -w "$h %{http_code} %{time_total}s\n" "https://$h/health"
done

Expected output: both return 200 with low totals; during a simulated origin failure the failed region returns 503 and traffic should already be shifting to the healthy one.

Cache invalidation storms inflate TTFB. Diagnosis: high-frequency purges trigger origin fetch fallbacks and cache stampede. Fix: serve stale-while-revalidate with s-maxage and platform cache tags so a single origin fetch repopulates the edge while stale content is served. Roll back by reverting to static asset routing via a DNS record swap.

Frequently Asked Questions

Does Cloudflare Workers have faster cold starts than Vercel Edge? Generally yes. Cloudflare pre-initializes V8 isolates across its network and typically returns sub-10ms cold starts, while the Vercel Edge Runtime forks a framework-aware runtime that may add 50–150ms on the first regional request, scaling with payload size. Measure your own numbers with the Step 2 procedure rather than assuming.

How do I accurately measure TTFB differences between the two platforms? Use curl -w "%{time_starttransfer}" from multiple geographic points, subtract time_namelookup and time_connect to remove DNS and network cost, and compare Server-Timing dur values to isolate execution overhead. Always bypass the cache with a unique query string so you are timing the isolate, not a HIT.

Can I route traffic between Vercel and Cloudflare for failover? Yes. Use DNS-based load balancing or an edge handler that checks the primary origin’s health via fetch() and returns a cached response or redirect when it sees a 5xx. Keep DNS TTL low so the failover converges within seconds, and verify with the multi-region probe in the troubleshooting section.

Which platform handles high-throughput API gateway workloads better? Cloudflare Workers usually wins on raw throughput and p99 tail latency thanks to lower per-isolate memory overhead and dense global isolate distribution. Vercel excels when you are inside the Next.js ecosystem, where build-time matcher compilation and first-class framework integration outweigh a slightly higher cold start.

Why is my benchmark showing a cache HIT instead of edge execution? Your request matched a cached entry. Append a random query parameter (?nocache=$RANDOM) and confirm cf-cache-status: DYNAMIC or x-vercel-cache: MISS before trusting any timing number.

Back to Vercel Edge Middleware