
Every D1 database we run has the same ceiling: 10 GB, hard, per database — a number that Cloudflare's own docs state plainly and that has not moved in 2025 or 2026 (opens in new tab). We hit it once in a test harness that recorded every HTTP request body for a week. The write failed with a laconic SQLITE_FULL. That's the deal you sign when you build on Cloudflare's data plane, and on balance we think it's a good one. This post is the honest tour of shipping the Teknora stack on Workers + Pages + D1 + Workers AI — and when we tell clients to go run Postgres on Hetzner instead.
We run the marketing site, blog, analytics dashboard, and chatbot for teknora.org entirely on Cloudflare. Three D1 databases bind into the Worker, plus Workers AI for chat inference and R2 for image assets. The whole thing deploys in about forty seconds from git push to a globally cached response in Frankfurt.
The stack, and why
Next.js 15 App Router, compiled through @opennextjs/cloudflare (opens in new tab), deployed to Workers with a Pages-style asset binding. Persistence is three D1 databases via Drizzle ORM. The chatbot calls Workers AI directly through the binding. Everything is wired in wrangler.toml.
Two things pushed us here over the default Vercel-plus-Neon answer. First, latency in Germany: median TTFB from Munich dropped from ~180 ms on Vercel to ~60 ms on Workers, helped by Cloudflare's "shard and conquer" routing pushing warm-request rate to 99.99% (opens in new tab). Second, cost: the Workers Paid plan starts at $5/month with 10M requests, Workers AI runs on the same envelope at $0.011 per 1,000 Neurons with 10,000 Neurons per day free (opens in new tab), and egress is zero.
What works beautifully
Preview environments are free and instant. Every branch gets a preview URL with its own D1 binding (preview_database_id) and KV. Stakeholders click a link from a phone; it renders in 40 ms from London.
Deploys are fast enough to be boring. A full production build with the MDX pipeline and chatbot knowledge-base prebuild is about 40 seconds. Rollbacks via the dashboard are near-instant.
The binding model is quietly excellent. Instead of DATABASE_URL strings, you get typed bindings: env.TRACKING_DB, env.AI. No credentials in code, no connection management. The Workers AI binding is pleasant: await env.AI.run("@cf/meta/llama-3.2-3b-instruct", { messages }) with per-model token pricing (opens in new tab) often an order of magnitude cheaper than OpenAI at small-model sizes.
Cost at SMB scale is a rounding error. For Mittelstand workloads — a few hundred thousand requests a day, a few GB of data, a chatbot handling a hundred conversations — the bill is in the noise.
What is genuinely hard
D1 is SQLite under the hood — beautifully simple for reads, painful for concurrent writes.
D1 is single-writer, and you will feel it. A Worker can open up to 6 simultaneous connections (opens in new tab), but the database serialises writes. Global read replication via Sessions API reached public beta in April 2025 (opens in new tab) and helps a lot. For write-fanout workloads — analytics on every pageview, bulk-upsert consumers — you hit contention fast. Our TRACKING_DB workaround was to batch writes behind a Queue. It works, but it's not a pattern you'd need on Postgres.
The 10 GB ceiling is real, and sharding is on you. You can provision up to 50,000 databases per account on Paid (opens in new tab) and shard by tenant; public writeups document going from 10 GB to hundreds of GB via manual sharding (opens in new tab). But "manual sharding" means you write the routing layer, cross-shard queries, and migrations yourself — a real source of bugs, especially for reports that want to see all tenants at once.
Schema migrations are serviceable, not great. wrangler d1 migrations apply runs ordered SQL idempotently with a tracking table, and rolls back to the previous successful state on error (opens in new tab). What's missing versus Postgres tooling: no EXPLAIN for the migration, no online DDL, no convenient way to apply across dozens of tenant-sharded databases without scripting it yourself. The Drizzle community has been solving the last part with custom scripts (opens in new tab), and the wrangler feature request for multi-database apply (opens in new tab) is still open.
N+1 in an edge context is worse than you remember. Each query is a separate hop through the Worker runtime to the database shard. We've caught our own N+1s that moved a page from 80 ms to 600 ms. Batch or IN (...) everything; treat Promise.all over the D1 binding as a smell.
The Next.js on Cloudflare story
Used to be a mess, now actually fine. @cloudflare/next-on-pages was the original adapter and ate a large chunk of the feature surface. In 2024 Cloudflare switched to @opennextjs/cloudflare (opens in new tab), which runs Next's Node runtime inside a Worker; it went 1.0 in 2025 and is now the recommended path (opens in new tab).
What you get today: App Router, Route Handlers, dynamic routes, SSG, SSR, ISR, standard middleware, image optimisation, Partial Prerendering, after(), composable caching, Turbopack assets. What you don't, as of April 2026: Node Middleware from 15.2 is not yet supported (opens in new tab), the Edge runtime of Next.js isn't either, and the Worker size ceiling is 3 MiB compressed on Free, 10 MiB on Paid (opens in new tab). One specific bite: next-mdx-remote produced a bundle the RSC prerender couldn't serialise; we migrated to next-mdx-remote-client. Half a day of yak-shaving — the sort of thing you eat when you run the adapter instead of Vercel's native integration.
EU data residency in practice
This is where Cloudflare went from "maybe" to "usable" for DACH Mittelstand clients during 2025. As of November 2025, D1 supports jurisdiction pinning at database creation (opens in new tab) with eu and fedramp. Create with npx wrangler d1 create db-name --jurisdiction=eu and the database — including any read replicas, constrained to the same jurisdiction (opens in new tab) — runs only inside the EU. The jurisdiction is immutable.
Two caveats for an auditor. The Worker that queries a pinned database can still run anywhere globally — jurisdiction constrains where the data lives, not the compute. Have a sentence ready for the DPIA. And R2 has its own jurisdiction story (EU works), KV does not yet offer hard pinning, and Workers AI execution is model-dependent. For strict residency — healthcare, legal records — read the Data Localization Suite (opens in new tab) docs and budget for Enterprise. For a marketing site, chatbot log, and campaign tracker, the defaults are fine.
When we tell clients to leave
Heavy OLTP with write contention. Thousands of writes per second against a single logical dataset with strong consistency? Postgres on Hetzner with a read replica, or Neon (opens in new tab) for serverless ergonomics, will be less painful. D1's single-writer is fine for most SaaS; not for a POS booking inventory against concurrent tills.
Long-running jobs. Workers Paid now runs up to 5 minutes CPU per request and 15 minutes on Cron Triggers or Queue Consumers (opens in new tab), a big jump from the old 30-second default. What it doesn't cover: a nightly ETL shuffling 50M rows, a video transcode, a 2000-page PDF pipeline. For those we run Hetzner with Coolify — a real line in SMB cloud economics (opens in new tab) at €5–10/month.
Postgres-specific features we actually use. Row-level security, LISTEN/NOTIFY, partial indexes, jsonb with GIN, logical replication, PL/pgSQL triggers. Emulating these on D1 exceeds the edge benefit. We've migrated one small project off D1 for exactly this reason.
Reporting and analytical queries. D1 is OLTP SQLite, not a warehouse. The 30-second query limit and row-scan cost mean a pivot across all accounts will time out. For that we sync into ClickHouse (opens in new tab), DuckDB on object storage, or Cloudflare's Analytics Engine.
A short decision checklist
We use a one-page checklist when scoping client projects:
- Is per-tenant data likely to stay under 10 GB for five years? If no, plan sharding from day one or pick Postgres.
- Are writes bursty-and-concurrent? Put a Queue in front.
- Need a reporting layer over operational data? Plan the sync path before picking the primary store.
- Does the contract mention data residency? EU jurisdiction on D1 covers a lot; Hetzner in Nürnberg covers the rest.
- Long-running job in the domain model? Don't make the Worker own it — Cron + Queue + a Hetzner worker, or a Workflow.
- Is the team already fluent in Postgres? Boring tech the team can operate at 2 a.m. beats clever tech they can't.
Of the last six Mittelstand engagements, four ran beautifully on Cloudflare, one ran on Hetzner because the workload was ETL-shaped, and one ran hybrid — edge web tier, Hetzner-managed Postgres behind a private tunnel for the OLTP core. All three were cheaper and faster to ship than the reflex "Vercel plus AWS RDS." The edge is not a silver bullet; it's a specific architectural stance, and the most valuable thing a senior engineer brings is knowing which side of the line you're on before the contract is signed.
Further reading
- Cloudflare D1 platform limits (opens in new tab) — the authoritative list; skim it before any D1-based design.
- Cloudflare Workers platform limits (opens in new tab) — CPU time tiers, subrequest counts, memory.
- D1 read replication deep-dive (opens in new tab) — how the Sessions API maintains sequential consistency across replicas.
@opennextjs/cloudflaredocumentation (opens in new tab) — supported Next.js features and known caveats.- D1 jurisdictions changelog (Nov 2025) (opens in new tab) — EU and FedRAMP data pinning.
- "Shard and Conquer" cold-start post (opens in new tab) — the 99.99% warm-rate story.
A random post, once a week.
Enter your email and we'll send you a handpicked article from our archive — no sales, no spam.
Roughly one email per week. Unsubscribe with one click.
Related posts

The 35% Flip: Why Teams Are Replacing SaaS With Custom Builds
The build-vs-buy math changed in 2024–2026. Here are the SaaS categories flipping first, a break-even model, and the ones to keep.

Agents Aren't Users: The Identity Crisis Behind 88% of Enterprise AI Incidents
Most enterprise AI incidents aren't prompt injection. They're agents running with scopes they should never have had, under credentials no one can revoke cleanly.

Comprehension Debt Is the Real AI Tax
AI-assisted engineers score 17% lower on comprehension of their own code. The codebase looks fine. The humans who shipped it can no longer reason about it under pressure.