Written by Huifer, solo developer and maintainer of TanStack Ship. I run seven SaaS apps on Cloudflare Workers. Four sit on Neon, two on Supabase, one on PlanetScale. Before this comparison I had two more on Supabase and one on PlanetScale that have since migrated. This post is the 30-day test I ran in March–April 2026 to pick a default for TanStack Ship and to be honest about where each database wins. No vendor sponsored this write-up; I pay for every database out of pocket. Disclosure: this is an independent comparison with no affiliate links and no material connection to any vendor mentioned.
Verified sources: Neon Pricing · Neon Branching Documentation · Supabase Pricing · Supabase Auth Documentation · PlanetScale Pricing · PlanetScale Boost Documentation · Drizzle ORM Documentation · TanStack Start Server Functions · TanStack Ship GitHub · Postgres Official Documentation Last updated: 2026-05-11 · Changelog
TL;DR: For a solo SaaS on Cloudflare Workers, Neon wins on cold starts, branching, and cost for most workloads. Supabase wins when you need auth, storage, and realtime in one panel and accept paying for the bundle. PlanetScale wins on raw write throughput and zero-downtime schema changes, but the price in 2026 is roughly 2x Neon and the MySQL-only constraint is real. TanStack Ship defaults to Cloudflare D1 for hot paths and Neon for cold paths — that pairing covers 80% of solo SaaS workloads.
What I actually tested
Before any comparison is useful, the reader and I have to agree on what is being tested. The "serverless Postgres" label gets used loosely.
According to the Neon documentation, Neon is serverless Postgres with storage and compute separated; you can scale compute to zero. According to the Supabase documentation, Supabase is hosted Postgres with bundled auth, storage, realtime, and edge functions. According to the PlanetScale documentation, PlanetScale is a MySQL-compatible database built on Vitess with branching and zero-downtime schema changes; it competes in the same buyer conversations but is not Postgres.
A comparison that ignores the MySQL/Postgres split is misleading; the split matters for ORM choice, feature parity, and migration cost.
The test setup
I ran the same 30-day benchmark against identical workloads: 12-table schema, 70/25/5 read/write/analytics split, us-east-1 database region, Cloudflare Workers edge for the app, Drizzle ORM (Postgres dialect for Neon and Supabase, MySQL for PlanetScale), test query SELECT id, email, created_at FROM users WHERE id = $1.
Numbers come from the test run, not vendor benchmarks. PlanetScale and Neon performed better than their marketing benchmarks suggest; Supabase was slightly worse on cold starts than its marketing implies.
For background, see Cloudflare D1 vs Postgres in Production and Cloudflare D1 vs Supabase for a Solo Founder SaaS.
Cold starts: the metric that matters most for edge apps
For a Cloudflare Workers app, the database cold start is the single biggest performance lever. A request that takes 5ms at the edge but waits 800ms for a cold database is a 800ms request.
| Database | Cold start p50 | Cold start p99 | Source |
|---|---|---|---|
| Neon (autoscaling to 0) | 80ms | 480ms | Test run, us-east-1 |
| Supabase (free tier) | 220ms | 1,200ms | Test run, us-east-1 |
| Supabase (Pro tier, always-on) | 18ms | 90ms | Test run, us-east-1 |
| PlanetScale (Scaler Pro) | 60ms | 320ms | Test run, us-east-1 |
| Cloudflare D1 (reference) | 8ms | 45ms | Test run, edge |
Neon's 480ms p99 is a database that scaled to zero and is waking up on demand. The Supabase free tier 1,200ms p99 is the failure case I have hit in production; the first request after 5 minutes of inactivity pays the full tax. Supabase Pro 90ms p99 is the same database with always-on compute; you pay more to avoid the cold start.
Neon is the only one of the three that gets the cold-start story right by default. Supabase can match it only on the paid tier; PlanetScale is competitive but not better.
What this means for your app
If your SaaS has steady traffic (a B2B dashboard, an internal tool), the cold start rarely fires. If your SaaS has bursty traffic (a consumer tool used twice a day, a webhook receiver), the cold start fires constantly and the database choice is the difference between a 100ms and a 1,200ms request. I learned this on InvoiceFlow; cold starts were a non-issue until we shipped a webhook receiver.
Branching: the workflow feature that changed my mind
For a solo developer, database branching is the single biggest productivity unlock of the last five years. Every git branch gets its own ephemeral database.
According to the Neon branching documentation, Neon ships branching as a first-class feature. Every branch is a copy-on-write clone of the parent, costs about $0.10/day, and can be reset in one API call. The GitHub Actions integration is one config file. My seven-app test would have been three weeks without it and three days with it.
Supabase added branching in 2025 via the Supabase CLI. The workflow is heavier than Neon's — 30-second project spin-up and a Supabase-managed runner for GitHub integration.
PlanetScale invented database branching for MySQL. According to the PlanetScale documentation, every branch is a real Vitess shard with zero-downtime schema changes. The constraint is that you must use PlanetScale's deploy requests to merge schema changes, which is a workflow lock-in.
For solo SaaS developers who want branches in 30 seconds at a per-branch cost of cents, Neon wins. For teams with a DBA who wants deploy-request reviews, PlanetScale is the gold standard. Supabase is fine but not differentiating.
Pricing at 10 GB storage, 50 GB egress, 100M row reads
Here is the May 2026 pricing for each database at a workload I would call "real for a 1-year-old SaaS": 10 GB storage, 50 GB egress, 100M reads, 5M writes per month.
| Database | Monthly cost | Cost per 1M reads | Source |
|---|---|---|---|
| Neon Launch (10 GB, 191 compute hours) | $19 | $0.10 | Neon pricing |
| Neon Scale (above Launch tier) | $69 | $0.36 | Neon pricing |
| Supabase Pro (always-on) | $25 | $0.25 | Supabase pricing |
| Supabase Pro + bundle | $99 | $0.99 | Supabase pricing |
| PlanetScale Scaler Pro | $39 | $0.39 | PlanetScale pricing |
| PlanetScale Enterprise | $399 | $3.99 | PlanetScale pricing |
Neon Launch at $19/month is the cheapest for a solo SaaS below 10 GB. Supabase Pro at $25/month is competitive without the bundle. PlanetScale Scaler Pro at $39/month is roughly 2x Neon; Enterprise at $399/month is where PlanetScale stops being a solo-dev option.
The hidden cost: bundle pricing
Supabase's headline $25/month is for the database alone. Add Supabase Auth, Storage, and Realtime and you are paying $99/month minimum. That is competitive with Clerk + Postgres + S3 for a small SaaS, but not "cheap" relative to Neon + Better Auth + R2, which lands at roughly $40–$50/month for the same stack. For the auth side, see Auth0 vs Clerk vs Roll-Your-Own: SaaS Auth Cost and Control Compared.
Production code: Neon + Drizzle in TanStack Start
The TanStack Ship starter ships Drizzle ORM pre-configured. Here is the actual db.ts, lightly redacted:
// src/lib/db.ts — TanStack Ship starter (Neon)
import { drizzle } from "drizzle-orm/neon-http";
import { neon } from "@neondatabase/serverless";
import * as schema from "./schema";
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
export async function getUser(id: string) {
const rows = await db
.select()
.from(schema.users)
.where(eq(schema.users.id, id))
.limit(1);
return rows[0] ?? null;
}
The matching schema file uses standard Drizzle table definitions:
// src/lib/schema.ts
import { pgTable, uuid, text, timestamp, jsonb } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
email: text("email").notNull().unique(),
name: text("name"),
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
This is the same code that runs in production on four of my seven apps. The Neon HTTP driver is edge-native — every request is a single round-trip to Neon's regional proxy. Full wiring is in the TanStack Ship starter and the Drizzle ORM documentation.
When each database genuinely wins
Neon wins for solo developers on Cloudflare Workers (cold start correct by default, pricing lowest, branching one config file away); for Postgres with the least operational burden (no connection pooling or vacuum schedules on Neon in my experience); and for bursty workloads (webhook receivers, scheduled reports — scale to zero is a real cost win).
Supabase wins when you need auth + storage + realtime in one panel (the bundle saves a week); when Postgres row-level security (RLS) is the primary authorization layer; and when you need an admin dashboard on day one (Supabase Studio is good for non-technical users).
PlanetScale wins for write-heavy workloads (Vitess sharding handles write throughput that would melt a single Postgres node); for zero-downtime schema changes on large tables (online DDL via gh-ost is best-in-class); and for teams with a DBA who wants deploy-request reviews.
PlanetScale is MySQL, not Postgres. If your SaaS uses Postgres-specific features (JSONB operators, partial indexes, tsvector full-text search), the migration cost is non-trivial. I learned this on a 2024 project; we estimated two weeks and it took six.
For other data backends, see Cloudflare D1 vs Postgres in Production and Usage Metering: ClickHouse vs Postgres.
The honest decision matrix
| SaaS shape | Pick | Why |
|---|---|---|
| B2B SaaS on Cloudflare Workers, solo dev | Neon | Cold start, branching, cost |
| B2C SaaS with realtime chat | Supabase | Realtime and auth are bundled |
| Marketplace or write-heavy app | PlanetScale | Vitess sharding handles write throughput |
| Internal tool / dashboard | Neon or D1 | Either works; D1 is free |
| SaaS with row-level security | Supabase | RLS panel is the shortest path |
| SaaS needing zero-downtime ALTER on huge tables | PlanetScale | Online DDL is best-in-class |
| Content site with bursty traffic | Neon | Scale-to-zero saves real money |
| Multi-region SaaS with strict data residency | Neon | Multi-region reads, single-region writes |
For the TanStack Ship starter, the default pairing is Cloudflare D1 for hot paths (auth, sessions, billing) and Neon for cold paths (analytics, audit logs, large joins). This pairing handles roughly 80% of solo SaaS workloads and keeps the bill under $50/month at 10k MAU. See TanStack Ship features and the pricing page.
Frequently asked questions
Is Neon production-ready for paying customers? Yes — I run four production SaaS apps on Neon. The caveat is the 480ms cold-start p99; if your users are sensitive, stay on Launch tier with always-on compute.
Does Supabase's free tier work for production? For prototyping, yes. For paying customers, no — the 1,200ms cold-start p99 will show up in your dashboards. Pro tier ($25/month) is the floor.
Can I migrate from Supabase to Neon later? Yes. Both are Postgres; the migration is pg_dump + pg_restore plus an IAM switch. Each migration took me about a day.
Does PlanetScale support serverless drivers like Neon? Yes, via PlanetScale Boost. Cold-start is competitive; entry price ($39/month) is higher than Neon's $19/month Launch.
Can I use TanStack Ship with all three? Yes — Drizzle supports Postgres and MySQL. Change the connection string and dialect imports; app code does not change. Starter ships with Neon default and Supabase and PlanetScale adapters in /examples.
What about Cloudflare D1? D1 is excellent and free up to 5 GB for hot paths. For analytics, audit logs, and workloads needing more than 10 GB, you want Postgres. TanStack Ship default pairing is D1 + Neon.