← // all posts
field notes // architecture

Why I Build on Cloudflare Workers

2026-06-26 // Nicholas Rushing // 8 min read

Every project starts with the same quiet decision that nobody in the kickoff meeting asks about: where is this thing going to run? For the bulk of what I build — business sites, web platforms, the API behind a mobile app — the answer has settled into a default. It runs on Cloudflare Workers, with D1 at the data layer and email routing handling the rest. This post is the reasoning behind that default, including the parts where it's the wrong choice.

I want to be clear up front that this isn't platform evangelism. There's no architecture that's correct for every project, and anyone who tells you otherwise is selling something. What follows is why the edge model fits the kind of work I do, and where I happily reach for something else when it doesn't.

The problem with the default default

The reflex answer for a small business build used to be a virtual server somewhere — a box you SSH into, install a runtime on, point a domain at, and then quietly become responsible for forever. That box has an operating system that needs patching. It has a single location, so a visitor on the other side of the country eats the latency. And when it falls over at 2am, it falls over completely, because there's exactly one of it.

For a solo practice, that operational tail is the real cost. A client doesn't pay me to keep a Linux box patched for three years after launch — they pay for the thing the box runs. Anything that shrinks the surface I have to babysit is worth serious consideration, and the edge model shrinks it dramatically.

What the edge model actually buys

A Worker is a function that runs in every Cloudflare data center, close to whoever's requesting it, with no server for me to provision or patch. That single shift cascades into a handful of concrete wins:

D1 at the data layer

Workers on their own are stateless, so the interesting question is always: where does the data go? For most of my projects the answer is D1, Cloudflare's SQLite-backed database that binds directly into the Worker. It's a real relational store with prepared statements and migrations, which means I get parameterized queries by default and a schema I can version in git alongside the code.

// migrations live in the repo, applied on deploy
await env.DB.prepare(
  `INSERT INTO feedback_entries (name, project, rating, published)
   VALUES (?, ?, ?, 0)`
).bind(name, project, rating).run();

The published column defaulting to 0 in that snippet is deliberate — new submissions land unpublished and wait for review rather than going live the moment a stranger hits submit. That's the kind of decision that's trivial to make when your schema is in version control and not buried in some console UI.

SQLite at the edge sounds like a toy until you realize how many real applications never actually outgrow it. Most small-business workloads are reads, and reads are exactly what this model is good at.

The honest tradeoffs

Now the part the platform's own marketing tends to skip. The edge model is genuinely the wrong choice for some work, and pretending otherwise would just set a project up to hit a wall six months in.

How I actually decide

The test I run on every project is simple: is this mostly request/response work against read-mostly data, served to humans in a browser or a mobile app? If yes — and for a small-business site or a typical SaaS front end, it almost always is — the edge model wins on operational simplicity, performance, and the size of the surface I have to keep secure over the life of the project.

When a project leans the other way — sustained compute, heavy writes, a hard dependency on the Node ecosystem — I say so early and pick the tool that fits. The platform is a means to the client's outcome, never the point. But for the shape of work that comes across my desk most often, Workers plus D1 is the default I'd have to be argued out of, not into.

// thinking about your stack

If you're starting a build and want a straight answer on where it should run — edge, server, or something in between — that conversation is free. Let's scope it.

transmit_message