MW
Home Blog How I run 46 sites in one monorepo on Cloudflare Workers

How I run 46 sites in one monorepo on Cloudflare Workers

Forty-six sites, one repo, one platform layer. The architecture that keeps it from becoming forty-six unmaintainable forks.

Mahesh Waghmare
Mahesh Waghmare
9 min read
Share:
This is a comprehensive guide based on real-world experience and best practices from production projects.

HOW I RUN 46 SITES IN ONE MONOREPO…

Advertisement

The empire is a single git repo containing 46 sites, 32 APIs, and 15 shared packages. Everything ships to Cloudflare — Pages for the sites, Workers for the APIs, D1 for the databases, R2 for the assets. One billing account, one deploy script, one wrangler config per node.

People keep asking how this stays manageable solo. It’s mostly about the platform layer. The folder structure does some of the work, but the real lock is a tiny set of central Workers that every site delegates to.

The shape

The folder naming is canonical: folder name equals deploy domain. apps/sites/maheshwaghmare.com/ ships to https://maheshwaghmare.com. apps/apis/private/platform/notify.surror.workers.dev/ ships to https://notify.surror.workers.dev. No translation, no guessing.

The three rules

After a year of mistakes that almost killed this setup:

The platform layer

Ten Workers do the heavy lifting for every site:

WorkerWhat it does
auth.surror.workers.devJWT issuance, OAuth, app passwords
keys.surror.workers.devAPI key creation + verification
credits.surror.workers.devPer-user credit balance + spend
billing.surror.workers.devRazorpay webhooks, invoice generation
email.surror.workers.devResend wrapper with template registry
notify.surror.workers.devNewsletter subscriptions + fan-out notifications
media.surror.workers.devR2 uploads + image transforms
usage.surror.workers.devPer-customer usage metering
voting.surror.workers.devUp/down vote primitive for community sites
attribution.surror.workers.devUTM + referral tracking

A new site that needs auth + email + billing inherits all three by adding three secret tokens to its wrangler.jsonc and calling the central Workers via fetch(). Time to wire up auth for a new SaaS: under an hour.

The deploy script

There’s no fancy CI. Each node has a per-folder package.json with a deploy script. The repo-level helper just loops:

# Deploy a single site
cd apps/sites/maheshwaghmare.com
npm run deploy

# Or the entire site fleet (rarely needed)
npm run deploy:sites

Wrangler is the workhorse. Cloudflare’s edge handles the parallelism. The slow part is astro build per site (8-15 seconds), not the deploy itself. With 46 sites, a full rebuild is 6-12 minutes — but I almost never run a full rebuild. Each site is independent.

The empire isn’t 46 sites I maintain. It’s 10 platform Workers I maintain, with 46 thin clients on top.

— me, every time someone asks

The secrets dance

Every platform Worker has a SERVICE_AUTH_SECRET. Every consumer Worker has the same bytes stored as AUTH_VALIDATE_SERVICE_TOKEN. Rotating these is annoying — touch one, you have to touch every consumer.

The fix: a single rotation script at scripts/rotate-keys.js. Maps each “chain” of paired secrets and runs wrangler secret put against every node in the chain with the same generated hex. The whole rotation is one command:

node scripts/rotate-keys.js auth-chain --execute

Without that script, rotation was a 15-step manual process across 8 Workers, and I’d inevitably miss one. Most empire-scale problems aren’t about scale — they’re about repetition becoming a step you forget.

The mistakes that almost broke this

Three things I did wrong before I figured out the architecture:

What the architecture costs

Honest list of overheads:

  • Cognitive load when bootstrapping a new site. You can’t just npx create-astro — you need to know which platform Workers to bind, which package to extend, which secrets to set. Worth it after site #3, hard before that.
  • Cross-cutting changes need discipline. Changing the auth contract means touching every consuming Worker. The rotate-keys script + a shared TypeScript type help, but it’s still real work.
  • Secret lifecycle. Ten platform Workers each have 3-5 secrets. That’s 30-50 entries to manage. The state lives in wrangler secret put (encrypted server-side) and a gitignored .rotate-keys-state.json (local helper).

Monorepo architecture is exhausting at site three and joyful at site twenty. The middle is where most people quit.

— six months into the empire

What I’d tell past-me

If you’re standing up your first multi-site setup on Cloudflare:

  1. One Cloudflare account, one billing card. Non-negotiable.
  2. apps/sites/<domain>/ and apps/apis/<...>/<domain>/. Folder name = deploy domain. Skip “translation” layers.
  3. One canonical metadata file per node. No _PURPOSE.md graveyards.
  4. Platform Workers before product Workers. Build auth + email + billing once. Every product after that inherits.
  5. A rotation script BEFORE you need to rotate. You’ll need it. Have it ready.

The architecture won’t feel like a win on site three. It’ll feel like a win on site thirteen.

Get weekly notes in your inbox

Practical tips, tutorials and resources. No spam.