All posts

Guides / 2026-09-24

Cloudflare Workers + D1 vs Vercel + Postgres for a SaaS: why ShipKit chose Cloudflare

What it costs to run a SaaS on Workers, D1 and R2 versus Vercel and a hosted Postgres, at 1,000 and 10,000 users, with the arithmetic. What Cloudflare gives you, what it takes away, and when to pick the other side.

Cloudflare Workers + D1 vs Vercel + Postgres for a SaaS: why ShipKit chose Cloudflare

Most SaaS starters assume Vercel and a hosted Postgres. ShipKit runs on Cloudflare instead: Workers for the app, D1 for the database, R2 for files. It is the decision that shaped everything else in the template, including the framework. This post lays out the reasoning: the money, the developer experience, and the parts where Cloudflare is genuinely the worse choice.

All prices below come from the vendors' own pricing pages, checked on September 24, 2026. Pricing changes; check before you commit.

The bill

Two rough scenarios. The assumptions: about 10 ms of CPU per request, a typical read-heavy app, 10% of requests serving a file.

~1,000 users/month~10,000 users/month
Requests300k5M
Database1 GB5 GB
Files5 GB50 GB
Cloudflare (Workers Paid + D1 + R2)about $5about $6–7
Vercel Pro + Neon + Vercel Blobabout $33–40about $60–80

How the numbers work out:

  • Cloudflare's $5 is the Workers Paid minimum, and for a product this size it covers nearly everything. It includes 10M requests and 30M CPU-ms a month. D1 on the paid plan includes 25 billion rows read, 50 million rows written and 5 GB of storage. R2 includes 10 GB of storage for free and never charges for egress. At 10,000 users, the only extras are a few cents of CPU time and the storage above R2's free 10 GB.
  • Vercel Pro is $20 per deploying seat, which includes $20 of usage credit. Both scenarios fit inside that credit. At 5M requests, though, you pass the 1M CDN requests Pro includes, and the next tier costs $20 more.
  • Neon is the biggest variable. Storage is cheap. Compute is billed by the hour it runs: an app with steady traffic keeps it awake, and that comes to roughly $13–40 a month in these scenarios, depending on compute size. Supabase Pro ($25/month, with 8 GB of database and 100 GB of file storage) is a reasonable alternative and would replace Blob as well.

Before paying anything, the gap is wider still. Workers' free plan (100,000 requests a day) will run a pre-launch product at $0. Vercel's Hobby plan is limited to non-commercial use by Vercel's own fair-use rules, and taking payments counts as commercial. A SaaS that charges needs Pro from its first sale.

None of these sums is large. A founder who gets real value from Vercel should not switch over $50 a month. But the difference is not zero, and it compounds. Every preview project, side project and demo on Cloudflare shares the same $5.

What you get besides the price

Bindings instead of credentials. In a Worker, D1 and R2 are bindings: objects handed to your code by the runtime. There is no connection string to leak, no connection pool to size, no S3 key pair to rotate, and no "too many connections" error at a traffic spike. ShipKit reads every binding in exactly one file, and nothing else in the codebase knows how the database is reached.

The same runtime locally and in production. ShipKit's dev server runs your server code in workerd, the open-source runtime behind Workers, with local D1 and R2 behind the same bindings. "Works on my machine" means something here.

No egress fees on files. R2 doesn't charge for downloads. For a product that serves user uploads, avatars or generated files, that line item is usually the one that surprises you on other platforms.

Everything else is in the same account. Scheduled jobs are a line in wrangler.jsonc. Rate limiting is a binding (ShipKit uses it on public endpoints). DNS, TLS and a CDN come with the domain. There's one dashboard and one bill.

Global by default. A Worker runs close to each visitor. You don't pick a region for the app.

What it costs you

These are real, and each one has cost us time.

  • D1 is SQLite. It is fast and cheap, but it has no interactive transactions. db.transaction() throws, and the documented way to make several writes atomic is batch(). ShipKit is written around that: a conditional single statement for balances, a batch where several writes must land together. We wrote about it in D1 has no interactive transactions.
  • Size limits. A D1 database can be at most 10 GB on the paid plan. That is plenty for most SaaS products, and a hard ceiling for some.
  • No Postgres ecosystem. No extensions (pgvector, PostGIS, full-text search as Postgres does it), no row-level security, and the SQL is less capable. If your product needs any of those, D1 is the wrong database.
  • workerd is not Node. With nodejs_compat most packages work, but not all of them. Runtime eval is forbidden, which rules out some MDX and templating approaches. ShipKit already works around the ones it hit.
  • CPU limits per request. Long-running work, such as video processing or big exports, needs a different design (queues, or another service).
  • Lock-in. Bindings are a Cloudflare concept. Moving to another host later means replacing the database layer, file storage, cron and rate limiting. That is days of work, not an afternoon.

When to choose Vercel and Postgres instead

  • Your product needs Postgres features: extensions, heavy relational reporting, row-level security, or a database that will grow past 10 GB.
  • You need long-running server work that can't be broken into short jobs.
  • Your team already runs on Vercel and is productive there. A few dozen dollars a month buys a lot of familiarity.
  • You want Next.js with React Server Components in production. That pairing is at its best on Vercel.

If that's you, pick a Postgres-based starter from the start instead of fighting SQLite. Our comparison of TanStack Start boilerplates lists several.

Why ShipKit is Cloudflare-only

We considered supporting both. But a Drizzle schema is written for one database, so supporting both would mean two schemas, two migration histories and two copies of every feature's tests. Every future feature would have to be built twice, and the checks that keep the template honest would double with it. We would rather do one platform properly.

So ShipKit is for people who want what Cloudflare gives: a SaaS that costs about $5 a month until it has real traffic, with no connection strings and no egress bill. Auth, payments, an admin console and i18n are already running on it. See what's included, or open the live demo. It runs on the same stack.

More posts