If you've been living under a rock, Cloudflare Workers have quietly become the fastest way to run serverless code globally. They live on Cloudflare's 300+ edge datacenters, execute in 200ms cold start (vs Lambda's 1–2 seconds), and cost a fraction of what AWS charges for the same throughput. At Aidxn, we use Workers for: geo-redirects (Australia → /au), auth verification at the edge before page render, image optimization (Cloudflare Images API), and A/B test routing. They're not a Lambda replacement — they're smaller, faster, and cheaper for the high-volume light work that SaaS does constantly. Here's when to use them, four production patterns with code, KV + R2 storage, and the six questions everyone asks.
What Are Cloudflare Workers?
Workers are JavaScript/TypeScript functions that run on Cloudflare's edge network — not in a data centre, but on the same infrastructure that caches your site. When a user hits your domain, the request lands on the nearest Cloudflare edge server, your Worker code runs instantly, and a response comes back in milliseconds. No EC2 instance spin-up, no Lambda container cold start, no wait. The runtime is V8 (same JavaScript engine as Chrome/Node.js), so your code is portable. Workers support Node.js APIs (TextEncoder, crypto, fetch, streams, WebSockets), npm packages (via wrangler), and Cloudflare's storage APIs (KV for key-value caching, R2 for file storage, Durable Objects for stateful compute).
Cold start is ~200ms globally because the code is already in memory on edge servers. Lambda cold starts are 1–2 seconds because AWS has to spin up a container. For user-facing paths where latency matters (checkout flow, login, content delivery), 200ms beats 1500ms every time. Cost scales with CPU time and bandwidth: Workers cost $0.50 per million requests (invocations) plus storage fees. Lambda costs $0.0000002 per invocation plus $0.0000166 per GB-second. For small, quick tasks at scale, Workers win.
When Workers Beat Lambda
Lambda is full-powered: 15-minute timeout, any npm package, anything Node.js can do. Workers are lightweight: 30-second timeout (or 10ms CPU limit for "CPU intensive" tasks), but instant start and global distribution. Use Workers when latency matters and the task is simple. Use Lambda when you need full Node.js runtime and the user isn't waiting.
Workers shine at: (1) auth checks before page render — verify JWT in 5ms, no database needed; (2) geo-routing — read cf-ipcountry header, redirect in 2ms; (3) cache headers and CDN logic — modify response headers on the fly; (4) A/B test routing — hash user ID, serve variant A or B; (5) image resizing — Cloudflare Images resize on request, cache the result; (6) rate limiting — check Durable Objects counter, block or allow; (7) origin request modification — add auth headers before forwarding to your API. Lambda is best for: webhooks (async, no cold-start penalty), database writes, email sending, heavy computation, batch jobs.
Four Patterns: Code Examples
1. Geo-Redirect (Australia → /au)
User from AU lands on your marketing site. Redirect to region-specific content instantly. Worker reads the cf-ipcountry header (auto-injected by Cloudflare), checks the country code, rewrites the path. Execution: 2ms.
export default {
async fetch(request: Request, env: any): Promise<Response> {
const country = request.headers.get('cf-ipcountry') || 'US';
const url = new URL(request.url);
if (country === 'AU' && !url.pathname.startsWith('/au')) {
return Response.redirect(`https://aidxn.com/au${url.pathname}`, 301);
}
return fetch(request); // pass through to origin
}
};
2. Auth Check at Edge
Before rendering the dashboard, verify the JWT is valid. If not, redirect to login. No database round trip — just crypto. Worker extracts the token from the Authorization header, verifies the signature (pure computation, 5ms), and allows or denies the request.
export default {
async fetch(request: Request, env: any): Promise<Response> {
const auth = request.headers.get('Authorization');
if (!auth || !auth.startsWith('Bearer ')) {
return Response.redirect('https://aidxn.com/login', 302);
}
const token = auth.slice(7);
try {
// Verify JWT signature (no database needed)
const verified = await crypto.subtle.verify(
'HMAC',
await crypto.subtle.importKey('raw', new TextEncoder().encode(env.JWT_SECRET), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']),
Buffer.from(token.split('.')[2], 'base64url'),
new TextEncoder().encode(token.split('.').slice(0, 2).join('.'))
);
if (!verified) throw new Error('Invalid signature');
} catch {
return Response.redirect('https://aidxn.com/login', 302);
}
return fetch(request);
}
};
3. Image Transform (Resize on Request)
User requests /images/logo.svg?w=200&h=200. Worker calls Cloudflare Images API to resize, caches the result, serves the optimized image. No pre-rendering needed — transform on demand.
export default {
async fetch(request: Request, env: any): Promise<Response> {
const url = new URL(request.url);
const width = url.searchParams.get('w') || '400';
const height = url.searchParams.get('h') || '400';
const imageUrl = url.pathname.replace('/images/', '');
const resizedUrl = `https://yourcloudflare.com/cdn-cgi/image/width=${width},height=${height},fit=scale-down/${imageUrl}`;
return fetch(resizedUrl, {
cf: {
cacheTtl: 86400, // Cache for 24 hours
minify: { javascript: true, css: true, html: true }
}
});
}
};
4. A/B Test Routing
Route users to variant A or B based on a hash of their ID. All logic at the edge; users see instant variant-specific pages. No client-side redirection, no flicker.
export default {
async fetch(request: Request, env: any): Promise<Response> {
const userId = request.headers.get('x-user-id') || 'anonymous';
const hash = await hashString(userId);
const variant = hash % 2 === 0 ? 'a' : 'b';
const url = new URL(request.url);
url.pathname = `/${variant}${url.pathname}`;
return fetch(url.toString());
}
};
async function hashString(str: string): Promise<number> {
const encoded = new TextEncoder().encode(str);
const hashBuffer = await crypto.subtle.digest('SHA-256', encoded);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.reduce((acc, byte) => acc + byte, 0);
}
Storage: KV + R2
Workers talk to two storage APIs. **KV** (key-value) is global, replicated on every edge server, with 100ms eventual consistency — perfect for caching auth tokens, rate-limit counters, feature flags. **R2** is Cloudflare's S3-compatible object storage for files. At Aidxn, we use KV to cache user permissions (no database round trip on every request) and R2 to store invoice PDFs for instant download.
KV example: store a user's permissions after login, expire in 1 hour. Next request, check KV before hitting Supabase.
// Store in KV
await env.KV.put(`user:${userId}:perms`, JSON.stringify(perms), { expirationTtl: 3600 });
// Retrieve from KV
const cached = await env.KV.get(`user:${userId}:perms`);
if (cached) return JSON.parse(cached);
Six FAQs
Can Workers call Supabase?
Yes. A Postgres round trip from an edge location takes ~20–30ms. Workers have no CPU limit for I/O (wall time is generous), so querying the database is fine. Combine with KV caching: check KV first, if miss, query Supabase and cache for 10 minutes. This cuts database load and drops latency to single-digit milliseconds on cache hits.
What's the 30-second timeout?
Workers run for up to 30 seconds wall time (elapsed time, including I/O). If your function does a 20-second API call and 5 seconds of computation, you're under the limit. CPU intensive tasks (crypto, compression) have a separate 10ms limit, but most code is I/O-bound (waiting on external services), which doesn't count against CPU. Check your task: if it's compute-heavy, use Lambda.
How much do Workers cost?
Free tier: 100K requests/day. Paid: $0.50 per million requests, plus storage (KV is $0.50/GB, R2 is $0.015/GB). For comparison, Lambda is $0.0000002 per invocation (~$0.20 per million) but cold starts cost time and user experience. Workers' flat rate means your cost is predictable and scales linearly with traffic, not execution time.
Can I use npm packages in Workers?
Yes, with limits. Wrangler (Cloudflare's CLI) bundles npm packages into your Worker. Avoid heavy dependencies (Lodash, Express, ORM libraries). Use lightweight packages (zod, date-fns, crypto libraries). The bundle must stay small because cold start depends on code size. For heavy dependencies, call Lambda instead.
How do I debug Workers locally?
Install Wrangler: npm install -g wrangler. Create a wrangler.toml file (project config), write your Worker in src/index.ts, then run wrangler dev. This spins up a local server that mimics the edge environment. You can test requests, read logs, and debug before deploying.
Is Cloudflare Workers better than Netlify Edge Functions?
They're different tradeoffs. Netlify Edge (Deno) has a 50ms CPU limit and is better for auth/redirects at massive scale (distributed across Deno Deploy). Cloudflare Workers have a 30-second limit and are better for mixed I/O + compute (image transforms, A/B routing, light webhooks). Pick based on your use case: if you need sub-50ms crypto-only tasks, Netlify. If you need to call external APIs, transform data, and cache, Cloudflare. See Netlify Functions vs Edge Functions for a deeper comparison.
The Bottom Line
Cloudflare Workers are the fastest, cheapest way to run code at the edge globally. Cold start is 200ms (vs Lambda's 1500ms+), cost is flat per request, and they integrate seamlessly with Cloudflare's CDN. For Velocity X and other SaaS products, they're perfect for: geo-redirects (instant regional content), auth checks (no database latency), image transforms (on-demand resize), A/B routing (instant variants), rate limiting (distributed counters), and origin request modification (add headers before forwarding to API). They're not a Lambda replacement — they're a different tool for different work. Use Workers for the fast path, Lambda for the slow path. For more on serverless architecture, read about SaaS infrastructure decisions, and get consulting on serverless-first SaaS from Aidxn.