Six months ago I moved my side project (a link-in-bio tool with ~2,000 daily users) from Vercel Hobby to Cloudflare Workers Free. Not because Vercel is bad — it's excellent — but because I read the fine print: Hobby is non-commercial only, and I was about to add a tip jar.
Here's the honest 6-month report.
The Setup
- Before: Vercel Hobby, Next.js app, ~50GB bandwidth/month
- After: Cloudflare Workers + Pages + KV + R2, same app rewritten as a Worker
- Migration time: One weekend (8 hours, mostly rewriting API routes as Worker handlers)
The Bill
| Month | Vercel (projected) | Cloudflare (actual) |
|---|---|---|
| 1 | $20 (bandwidth overage) | $0 |
| 2 | $20 | $0 |
| 3 | $40 (added tip jar → commercial) | $0 |
| 4 | $40 | $0 |
| 5 | $40 | $0 |
| 6 | $40 | $0 |
| Total | $200 | $0 |
Cloudflare Workers free tier: 100,000 requests/day, 10ms CPU time per request. My usage peaked at 67,000 requests/day. Never hit the limit.
Performance Numbers (Real, from Cloudflare Analytics)
| Metric | Vercel (before) | Cloudflare (after) |
|---|---|---|
| Cold start p50 | 340ms | 0ms (no cold starts) |
| Cold start p99 | 1.2s | 0ms |
| TTFB p50 (US) | 89ms | 23ms |
| TTFB p50 (EU) | 156ms | 31ms |
| TTFB p50 (Asia) | 410ms | 45ms |
The Asia number is the killer feature. Vercel runs in a few regions; Cloudflare runs in 300+ cities. My Singapore users went from 410ms to 45ms. That's not an optimization — it's a different product.
What Broke (The Honest Part)
-
Node.js APIs. Workers isn't Node.
fs,path,crypto(partially) don't exist. I had to replacebcryptwith WebCryptosubtle.digest. Took 3 hours of "why is this undefined" debugging. - No persistent connections. WebSockets need Durable Objects (paid tier). I punted and used polling. Users didn't notice, but it felt dirty.
-
KV consistency. KV is eventually consistent. I wrote a session token, immediately read it back, got
null. Spent 2 hours thinking my code was broken. It wasn't — KV just hadn't propagated. Solution: write to KV, return the value from memory, don't re-read. - 10ms CPU limit. Image resizing hit the limit. Had to move that to a queue + consumer Worker. More architecture, more moving parts.
The One Feature That Almost Made Me Switch Back
Preview deployments. Vercel's git-integration previews are magical: push a branch, get a URL, share it. Cloudflare has previews too, but they're per-branch and require Wrangler CLI setup. It's fine for me, but my non-technical co-founder couldn't figure out how to preview a change. Vercel's DX is genuinely better here.
The Uncomfortable Math
Cloudflare Workers free tier is 100,000 requests/day. Vercel Hobby is 100GB bandwidth. These sound comparable until you realize:
- A single API response is ~2KB. 100,000 requests = 200MB/day = 6GB/month.
- Vercel counts bandwidth, Cloudflare counts requests.
- My app is API-heavy, small payloads. Cloudflare wins.
- If I were serving video or large images, Vercel's bandwidth model might actually be cheaper.
The free tier that's better depends entirely on your payload shape. Nobody talks about this.
Would I Do It Again?
Yes, but with eyes open. The 6-month savings ($240) funded my domain renewal and a year of Proton Mail. The tradeoff is DX — I miss Vercel's polish, but I don't miss the invoice.
I sketched the migration checklist and the KV consistency workaround with MonkeyCode: https://ly.cyberserval.tech/iIETXiF
What's your free tier horror story? The one where you hit a limit at 2am and had to frantically upgrade?
Top comments (0)