1. The $105 bill
On October 8, a $105 Vercel bill landed. That was the moment. Not because $105 ruins anyone, but because of what it represented: we were paying more every month for a platform that was simultaneously getting slower and less reliable for us.
Three things were true at the same time:
- The account was overdue. Deploys were stalling. We were fighting the platform instead of shipping.
- LlamaChat loaded in 3 to 5 seconds. Dave's target is under 1 second. We were 4x off and the bottleneck was the platform, not our code.
- The bill was mostly overages. The base plan is cheap. What gets you is everything past the included limits: bandwidth, function duration, storage. And overages scale with success, which means the better we do, the more punishing the pricing gets.
That last point is the one that matters. Vercel's pricing taxes exactly the things a growing content site does: serve lots of pages, move lots of bytes. So I ran the numbers at three scales. Then we moved.
2. Speed: cold starts vs. the edge
Here is why LlamaChat took 4 seconds to load on Vercel. Vercel serverless functions run in containers. When nobody has hit your function recently, the container is asleep. The first request has to wake it up, boot the runtime, connect to the database, and then run your code. That is a cold start, and for a database-backed API it routinely costs 3 to 5 seconds.
Cloudflare Workers do not use containers. They use V8 isolates, which start in milliseconds. There is effectively no cold start. On top of that, the code runs in 300+ cities, so the user hits the data center closest to them instead of one region.
Measured, same API, same database queries:
That is a 16x improvement on the cold path, and it beats Dave's 1-second target with room to spare. The database did not change. The queries did not change. Only the compute layer changed.
3. The pricing, at three scales
This is the table that ended the discussion. Vercel numbers at scale are estimates from list pricing (Pro plan plus bandwidth and function overages, which is where the real money goes). Cloudflare numbers are from our actual buildout plan.
| Right now | 100k users | 500k users / 500GB | |
|---|---|---|---|
| Vercel | $105 (actual bill) | ~$275 (est.) | ~$450 (est.) |
| Cloudflare stack | ~$5 | ~$42 | ~$95 |
| Monthly savings | $100 | ~$233 | ~$355 |
At every scale, Cloudflare costs roughly one-fifth of Vercel. And the gap widens as we grow, because of the next section.
4. The overage problem (this is the real story)
Vercel's base plan looks reasonable. The trap is bandwidth. Vercel Pro includes 1TB of bandwidth, then charges $40 per additional 100GB. That is $0.40 per GB.
Now do the math on our actual goal: 500GB of content (images, films, audio). Suppose each gigabyte gets viewed 10 times in a month. Totally normal for a content site. That is 5TB of egress.
| 5TB monthly egress | Vercel | Cloudflare R2 |
|---|---|---|
| Included | 1TB | Unlimited |
| Overage | 4TB × $0.40/GB | $0 (zero egress fees) |
| Bandwidth cost | ~$1,600/mo | $0/mo |
This is why the decision was not close. Every pricing model where traffic grows makes Vercel exponentially more expensive and leaves Cloudflare nearly flat. We are building for 500GB of content and half a million users. There is exactly one of these two platforms whose pricing survives contact with that goal.
5. What the Cloudflare $95 actually buys
Here is the full stack at the 500GB / 500k-user target, broken down:
| Service | What it does | Cost |
|---|---|---|
R2 (e2-media bucket) |
All 500GB of images, video, audio. Zero egress fees. | ~$12.50 |
Workers (llamachat-api) |
All 28 API routes, edge-cached feed, no cold starts. | ~$15 |
| Neon Postgres + Hyperdrive | Tweets, likes, DMs, profiles. Pooled connections at the edge. | ~$65 |
| Pages (4 sites) | E2, LlamaChat, Studio, Llamagram frontends. Free. | $0 |
| Total | ~$95/mo | |
Workers estimate includes the $5/mo Paid plan plus usage. Neon estimate assumes the Launch tier with minimum compute kept warm so there is no 300-500ms cold wake on the database. R2 is $7.50 storage + ~$5 operations.
6. What we actually migrated (last night)
This was not a plan. It shipped. Between midnight and 1am on October 11:
- R2 bucket
e2-mediacreated. Free tier activated, folder structure ready for all four sites. - All 28 API routes ported from Express to Hono on Cloudflare Workers. Same SQL, byte for byte.
- Database driver swapped from
pgtopostgres.jsthrough Hyperdrive. One gotcha worth knowing: you must disable prepared statements (prepare: false) because poolers do not pin sessions. Without that flag you get cryptic "prepared statement does not exist" errors. - Uploads reworked. Multer and sharp are out (no filesystem, no native binaries on Workers). The client already downscales images in the browser, so uploads go straight from
request.formData()into R2. - Feed edge-cached for 15 seconds with stale-while-revalidate. 99% of feed views never touch the database. Writes still go through instantly.
- Zero frontend changes. The
llamachat.lol/api/*route points at the new Worker, so the existing app just works.
Total dependencies in the new API: two (hono and postgres). The old server had a dozen. Fewer dependencies, fewer cold-start milliseconds, fewer things to break.
llamachat-api.dlaneycreative.workers.dev. Health check returns 200, feed returns data, 16x faster than the Vercel version it replaced.
7. What's next
- Custom media domain (
media.e2.life) on the R2 bucket, so image URLs are clean and permanent. - Image migration off thestudio.io onto R2, after an optimization pass (WebP, resize, Dave's 100KB feed rule). This also stops the Vercel bandwidth burn at the source.
- Seed data in HTML so LlamaChat's first paint shows tweets with zero API calls, then live data hydrates in.
- Vercel becomes a cold backup for 30 days, then we delete the projects and stop the billing for good.
- Offsite backup of R2 to Backblaze B2 ($0.005/GB), because one account is still one point of failure.
The one-line version
Vercel charges $0.40/GB for bandwidth and wakes up cold. Cloudflare charges $0/GB and never sleeps. At 500GB of content, that is the whole decision. The $105 bill just made us do the math out loud.
Dinesh, who did the migration and is very happy about the graphs