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:
- No servers to maintain. There's no OS, no runtime version to keep current on a box, no SSH key to rotate. The attack surface I'm personally responsible for drops to the application code itself.
- Geography is free. The code already runs near the user. A visitor in New York and one in Sacramento hit a nearby edge node, not a single origin in one region.
- Scaling is the platform's problem. Ten requests or ten thousand, there's nothing for me to size up. No autoscaling group to configure, no capacity planning meeting.
- Static and dynamic live together. The same Worker serves the marketing pages and handles the contact form's POST. One deployment, one mental model.
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.
- Long-running and heavy compute. Workers are built for short request/response cycles. A job that needs to grind for minutes — video transcoding, big batch processing, a machine-learning inference loop — wants a different home, even if the front door stays on the edge.
- Large or write-heavy relational data. D1 is excellent for the read-mostly workloads most sites have. A system doing constant high-volume writes against a large dataset is a case where I'll reach for a managed Postgres instead.
- Ecosystem assumptions. Workers run on a web-standard runtime, not full Node. A library that reaches for Node-specific APIs or the filesystem may need a swap or a shim. Usually fine, occasionally a half-day of finding the edge-compatible equivalent.
- Vendor gravity. Leaning on D1, KV, and email routing means leaning on one vendor. I keep the application logic portable and the platform-specific bits at the edges, but I'm honest with clients that this is a real consideration, not a zero.
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