One Framework, Thirty Apps: What "Infinite Scale" Really Means on Cloudflare Workers
I run a fleet of around thirty web apps by myself, and they all share one architecture: a static Next.js frontend on Cloudflare Pages, a Cloudflare Worker for the API, and a D1 database. New projects are cloned from a single template repo, and shared packages handle auth and email so I never write a login flow twice.
I catch myself telling people this setup "scales infinitely." This post is me checking that claim against the actual architecture. The short version: the efficiency story is real and better than most people expect, and the infinite story is true for exactly half the stack. Knowing which half is the whole game.
Two deployments, joined by a fetch
Each app is two separate deployments. The frontend is a Next.js project built with output: 'export', which means every route becomes a plain HTML file at build time. There is no server rendering, no middleware, no API routes - just files, served from Cloudflare's CDN. The backend is a Worker: a small router that speaks JSON under /api/v1, talks to D1, and sends email. The two are joined by nothing but a cross-origin fetch guarded by CORS.
The framework is a template repo, not a package. A new app is a clone plus a config file, a D1 database id, and two secrets. Shared @latentfreedom packages carry the parts that must not drift: password hashing, JWT sessions, email templates, CORS, and error envelopes.
Why Workers are cheap to run and cheap to think about
The usual mental model for a backend is a server: a container or VM that boots in seconds, sits warm costing money while idle, and needs patching. Workers do not work that way. A Worker is a V8 isolate - the same lightweight sandbox Chrome uses for browser tabs - spun up inside a process that is already running on thousands of machines. Cold starts are single-digit milliseconds, not seconds, and an idle Worker costs nothing at all.
- Scale to zero, for real. A fleet of thirty apps where twenty-five get light traffic would be financial nonsense on containers. On Workers, an app that gets no requests generates no bill. That is the property that makes a solo fleet viable at all.
- Scale up is not my problem. There is no instance count, no autoscaler config, no load balancer. A traffic spike lands on Cloudflare's scheduler, not on a pager.
- Nothing to patch. No OS, no base image, no node version rotting on a forgotten VPS. The runtime is Cloudflare's job.
- The static half is even cheaper. Pre-rendered HTML on a CDN is the most scalable artifact in computing. The frontend of every app in my fleet could take an arbitrary traffic spike and the only observable effect would be a bandwidth graph.
What genuinely scales without limit
Three parts of this architecture really do have no practical ceiling for a fleet like mine:
- Static reads. Every page load is a CDN file. Effectively unbounded.
- Stateless auth checks. Sessions are JWTs, so verifying a request is pure cryptography inside the Worker - no database read required to know who is calling. Auth verification scales exactly as far as the Workers platform does.
- The compute tier. The Worker itself is horizontally scaled by Cloudflare across its whole edge. More requests means more isolates; my configuration does not change.
Notice the pattern: everything that scales without limit is stateless. That is not an accident of Cloudflare's design. It is the oldest rule in distributed systems - state is the hard part - and this stack simply pushes all of it into one place.
Where "infinite" quietly stops being true
All of that state lands on D1, and D1 is SQLite: a brilliant, boring, single-writer database. Cloudflare has added global read replication, but every write to a database still goes through one primary, and each database has a 10 GB cap. So the honest sentence is: my reads scale like a CDN; my writes scale like one very well-run SQLite file per app.
And in my current setup there are three self-inflicted multipliers on that constraint, all of which I can name because I went looking:
- Five apps share one database. By deliberate policy, a group of my dashboard apps use a single D1 with table prefixes instead of five separate databases. It keeps admin simple, but it means five apps' writes serialize through one primary and share one set of per-database limits. Worker scale-out does not help with that; it makes contention arrive faster.
- There is no cache tier yet. No KV, no Cache API headers on GET responses. Every authenticated page load does a fresh database round trip to load the user. At my traffic this is invisible; at 100x it would be the first fire.
- Compute runs at the edge, state lives in one place. Workers execute at whichever of Cloudflare's locations is nearest the user, while each D1 primary lives in one region. Smart Placement - the setting that moves the Worker next to the database it talks to - is still commented out across my fleet. Edge compute chatting with distant state is the worst latency shape, and I have the fix turned off.
There is also a scaling limit that has nothing to do with traffic: the template is copied, not depended on. Thirty apps each hold their own snapshot of the worker skeleton, and improvements propagate by an agent running a sync script, not by bumping a version number. That scales operationally only because automation does the walking.
| Layer | Scales by | Practical ceiling |
|---|---|---|
| Static frontend (Pages) | CDN | none that matters |
| Worker compute | isolates across the edge | none that matters |
| Auth verification | stateless crypto | none that matters |
| D1 reads | read replication | high, and growing |
| D1 writes | one primary per database | real, and shared by co-tenant apps |
The claim I am allowed to make
"Scales infinitely" is marketing. What this architecture actually does is subtler and, honestly, better: it scales to zero perfectly, which is what a thirty-app solo fleet needs every single day, and it scales up much further than any app I run is likely to demand - with a known, boring escape hatch at every layer when one of them outgrows it. Split the shared database. Turn on Smart Placement. Put KV or cache headers in front of hot reads. Each is an afternoon of work I get to defer until an app has earned the traffic that requires it.
Sources
- Cloudflare Docs - How Workers works - isolates vs containers, cold start model.
- Cloudflare Docs - D1 limits - per-database size and request limits.
- Cloudflare Docs - D1 read replication - global read replicas, single write primary.
- Cloudflare Docs - Smart Placement - moving compute next to state.