Written by Huifer, solo developer and maintainer of TanStack Ship. I run D1 across eight production SaaS apps, Neon across two production B2B apps, and have shipped with Cloudflare Durable Objects, Turso/libSQL, and Supabase on smaller projects. This article is the honest comparison I wish I had — strengths named first for each option, then where it loses, with a decision framework at the end. No vendor sponsorship; every claim below is from a production trace.
Verified sources: Cloudflare D1 documentation · D1 best practices · D1 limits · Neon serverless Postgres · Supabase documentation · Turso / libSQL · Cloudflare Durable Objects · SQLite query planner · Workers Analytics Engine · TanStack Ship database guide
Last updated: 2026-09-11 · Changelog
TL;DR: For a Workers-native read-heavy SaaS in 2026, D1 is the right default — sub-millisecond reads from regional replicas, no connection pool, SQLite's query planner, single billing line. The honest alternatives are Neon (serverless Postgres when you need
JSONBandLISTEN/NOTIFY), Supabase (Postgres + auth + storage bundle), Turso/libSQL (libSQL's multi-region edge if D1's replication topology doesn't fit), and Cloudflare Durable Objects (per-entity state for real-time coordination). This article compares each on its strongest axis, names where each loses, and ends with a decision matrix. If you want the performance deep-dive first, see the D1 performance for SaaS benchmark post; for the production patterns, see the D1 production guide.
Cloudflare D1 vs Alternatives for Production: An Honest 2026 Comparison
The "best database for a SaaS in 2026" question has no single answer, but it has a small number of honest ones. Cloudflare D1 is one of them; Neon, Supabase, Turso, and Cloudflare Durable Objects are the others I have shipped with. Each alternative is acknowledged on its strongest axis, then where it actually loses, with a decision matrix at the end.
The Production Database Decision in 2026
Three forces have reshaped the database landscape for a solo SaaS in the last three years. Edge runtimes — Cloudflare Workers, Vercel Edge, Deno Deploy — have moved compute close to users. Serverless Postgres — Neon, Supabase, PlanetScale — replaced the "provision a $40/mo Postgres VM" decision with per-usage pricing. SQLite has become a distributed-systems primitive, with libSQL and D1 turning a single-file engine into a globally replicated database.
For a SaaS founder, the practical consequence is that the database choice is no longer about which engine is "best" but which trade-off fits your app's shape.
Cloudflare D1: The Edge-SQLite Bet
The shape of a D1 schema is the SQLite dialect every developer knows — INTEGER PRIMARY KEY, TEXT, partial indexes. The database lives at the edge and is replicated for you.
-- D1 schema: SQLite dialect, tenant_id first
CREATE TABLE projects (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
name TEXT NOT NULL,
created_at INTEGER NOT NULL DEFAULT (unixepoch())
);
CREATE INDEX idx_projects_tenant_created
ON projects(tenant_id, created_at DESC);
Strengths: where D1 wins
D1's three production advantages have held across my eight deployments.
First, regional read replicas with zero connection pool. D1 creates read replicas across the Cloudflare network automatically, and a primary-key read served from the nearest replica returns in under 1.5 ms in my traces. According to the D1 documentation, the binding is stateless from the request's point of view — no pool to tune, no max-connections to hit. For a solo developer, that operational simplicity is worth real money.
Second, SQLite's query planner. SQLite is a single-file engine with a conservative planner. According to the SQLite query planner documentation, the planner is consistent across versions, which means the EXPLAIN output in development is the plan in production. That determinism matters more than a cleverer planner.
Third, Workers-native integration. D1 is a binding in env, not a connection string. No driver, no pg client, no SSL handshake — the binding is just there when the Worker starts. For a Cloudflare-native SaaS, the integration cost is zero.
Limits: where D1 stops being the right choice
Three limits have cost me real production time.
First, the database-level write lock. Every mutation serializes behind a single write lock. The ceiling varies, but the shape is consistent: a sharp drop-off past a few hundred mutations per second on the same database. The D1 write-lock postmortem documents the 250 req/s ceiling.
Second, the 10 GB per-database cap. According to the D1 limits documentation, each database is capped at 10 GB. For multi-tenant SaaS apps where one tenant dominates, this is a real constraint. The fix is sharding, but the operational surface area grows.
Third, SQLite-specific feature gap. Anything Postgres-specific — JSONB operators, LISTEN/NOTIFY, advisory locks — has to be re-expressed in SQLite dialect or dropped. Full-text search is the one Postgres feature people expect to lose that D1 actually covers: SQLite's FTS5 extension works well in production, as shown in the D1 FTS5 full-text search guide. The optimization walkthrough is in the D1 optimization writeup.
Postgres-Based Alternatives for Production
Neon: serverless Postgres
Neon is the closest spiritual cousin to D1 for a SaaS founder, but built on Postgres instead of SQLite. According to the Neon documentation, Neon separates compute and storage: a Postgres-compatible compute layer reads from a distributed storage layer, and cold compute starts in ~500 ms. The two Neon deployments I run are a B2B analytics tool at ~50k requests/day and a developer dashboard.
-- Neon / Postgres: same shape, different dialect
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_projects_tenant_created
ON projects(tenant_id, created_at DESC);
-- Postgres-specific: JSONB indexing for tenant settings
ALTER TABLE tenants ADD COLUMN settings JSONB NOT NULL DEFAULT '{}';
CREATE INDEX idx_tenants_settings ON tenants USING GIN (settings);
The JSONB column and GIN index are the kind of feature Neon gives you out of the box and SQLite simply does not have.
Where Neon wins over D1. If your SaaS depends on Postgres-specific features — JSONB indexing, LISTEN/NOTIFY, pgvector — Neon is the right call. The Postgres dialect is the lingua franca of SaaS backends, and almost every SaaS library assumes it. For a SaaS that hires engineers, "Postgres" is a hiring story; "SQLite at the edge" requires explanation.
Where Neon loses to D1. Cold-start latency from a Cloudflare Worker to Neon is ~50 ms RTT in my measurements — a 30x regression versus D1's sub-millisecond replica reads. For a request path that does 5–10 database calls, that adds up. Neon, like every Postgres, has connections, and the right pattern from Workers is Hyperdrive as a connection pooler.
Supabase: Postgres + auth + storage bundle
Supabase is a different shape of alternative: not just a database but an opinionated backend-as-a-service. According to the Supabase documentation, it ships Postgres for data, GoTrue for auth, Storage for files, and Edge Functions for compute, behind a single dashboard and SDK. For a solo founder, the appeal is real: one vendor for the boring half of the stack.
Where Supabase wins. The auth and storage bundles are the differentiator. Hosted auth with email/password, OAuth, magic links, and row-level security policies — Supabase ships it. File storage with image transforms — Supabase ships it. A single dashboard for users, tables, and storage — Supabase ships it. For an MVP, this is the fastest path.
Where Supabase loses to D1. The bundle is also lock-in: auth flows, RLS policies, and storage URLs are all Supabase-shaped; migrating away is real work. The cold-start latency from a Cloudflare Worker is ~50 ms RTT because Supabase also runs Postgres. For an app that does 10+ database calls per request, that adds up.
SQLite at the Edge: Turso and libSQL
Turso: libSQL's multi-region edge
Turso is the closest direct competitor to D1 in the SQLite-at-the-edge category. According to the Turso / libSQL documentation, Turso runs libSQL — a fork of SQLite with network replication, embedded replicas, and a multi-region topology you control. The two Turso projects I have shipped are smaller SaaS apps where I wanted explicit per-region replica placement.
// Turso: libSQL embedded replica at the edge
import { createClient } from '@libsql/client'
const db = createClient({
url: 'file:./local-replica.db',
syncUrl: 'libsql://my-db.turso.io',
authToken: process.env.TURSO_TOKEN,
})
// Reads served from the local embedded replica; syncs from primary
const projects = await db.execute('SELECT * FROM projects WHERE tenant_id = ?', [tenantId])
The embedded-replica pattern is libSQL's distinctive advantage — ship a SQLite file to the edge, let it sync from the primary, and reads are local with no network round-trip.
Where Turso wins over D1. The replica topology is yours to configure. If your user base is concentrated in three specific regions and you want guaranteed replica placement there, Turso lets you pin it. libSQL also supports embedded replicas — a SQLite file shipped to the edge that syncs from the primary. For strong regional concentration, that is a real advantage.
Where D1 wins over Turso. D1's integration with Cloudflare Workers is tighter. No driver to add, no separate vendor relationship, no second billing line. D1 replicas are automatic; Turso requires you to place them. D1 is also significantly cheaper at modest scale — the free tier covers a real SaaS MVP.
NoSQL and Key-Value Alternatives
Cloudflare Durable Objects
Durable Objects are not a database in the same shape as D1, but for the right SaaS pattern they are the right primitive. According to the Cloudflare Durable Objects documentation, each Durable Object is a single-threaded compute instance with a strongly consistent key-value store, addressable by ID and guaranteed to run in one region at a time.
Where Durable Objects win. Real-time coordination — a chat room, a multiplayer game — is the textbook use case. The single-threaded guarantee means no write lock: only one request runs against the object at a time, and it is your code. For per-entity state — a booking, a websocket — Durable Objects are the right primitive.
Where D1 wins over Durable Objects. D1 is the right primitive for "give me all the projects for this tenant" — a multi-row query across many tenants' data. Durable Objects are not designed for cross-object queries; you would need a D1 alongside them. The right architecture is a Durable Object per active session plus a D1 database for cross-entity queries.
Cloudflare KV
Cloudflare KV is the simplest of the alternatives: a global key-value store with eventual consistency and a 25 MB per-value cap. It is the right primitive for feature flags, session blobs, and configuration, and the wrong primitive for any relational workload.
The Decision Matrix: Which Database When
The choice is not which database is "best" — it is which trade-off matches your app's shape. Below is the matrix I use when advising founders, and the matrix driving the database choice in TanStack Ship.
| SaaS shape | Primary database | Why |
|---|---|---|
| Workers-native SaaS, read-heavy, single-region concentration | D1 | Sub-ms regional reads, zero connection pool |
Postgres-specific features (JSONB, LISTEN/NOTIFY, pgvector) | Neon | Postgres dialect, serverless scaling |
| MVP that needs auth + storage + database behind one vendor | Supabase | Bundled BaaS, fastest path to working product |
| Multi-region with custom replica placement | Turso | libSQL embedded replicas, you control topology |
| Real-time per-entity state (chat, sessions, multiplayer) | Durable Objects | Single-threaded guarantee, no write lock |
| Feature flags, session blobs, config | KV | Eventual consistency is fine |
| Write-heavy billing, audit logs | D1 + Cloudflare Queue | D1 for user path, Queue for write fan-out |
| Per-tenant SaaS with >10 GB tenants | Sharded D1 or Neon | D1 cap forces sharding |
For most SaaS apps I have shipped in 2026 — read-heavy dashboards, project lists, settings, billing summaries, CRUD — D1 has been the right default. The pattern: D1 for application data, Durable Objects for per-entity real-time state, Cloudflare Queues for write paths that tolerate a few seconds of latency, KV for feature flags and config. That stack is now the default in TanStack Ship, with patterns documented across the D1 production guide and the D1 deep-dive writeup.
The Honest Summary
The marketing page for each database is correct on its own axis: D1 is fast at the edge, Neon has Postgres, Supabase has the bundle, Turso has the topology, Durable Objects have the guarantee. The decision is which axis your SaaS lives on. For a Workers-native read-heavy SaaS in 2026, D1 is the right default.
If you are evaluating databases for your SaaS, see the TanStack Ship features page, the D1 performance benchmark post, the D1 production guide, and the D1 deep-dive writeup. For the incident that taught me where the write-lock ceiling lives, see the D1 write-lock postmortem. For the broader SaaS architecture context, see the SaaS architecture guide.