edge-starter — Multi-Tenant SaaS on Cloudflare Workers
Production-shaped template for multi-tenant SaaS running entirely at the edge: Astro SSR, Hono API, Payload CMS on Workers, D1/R2/KV storage, and one-config client provisioning with monitoring and nightly backups.
Overview
edge-starter is a production-shaped template for running a complete multi-tenant SaaS entirely on Cloudflare Workers — no origin servers, no containers. It is a de-identified reference implementation derived from a live client deployment: an Astro SSR frontend with React islands, a Hono API, and Payload CMS running on Workers, all provisioned per client from a single config file.
The Challenge
Agency-style client sites usually accumulate a zoo of hosting setups. The goal was a repeatable recipe where onboarding a new client means editing one config and running one script — and every client gets the same production posture: isolated data, monitoring, backups, spam protection, and a CMS.
Architecture
Each client gets five isolated workers:
Key Features
- Frontend — Astro SSR with React islands, service-bound to the API and CMS (no public API surface, no CORS)
- API — Hono with sessions, RBAC, and rate limiting on D1 + KV
- CMS — Payload via OpenNext on Workers, with R2 media and on-the-fly image resizing
- Monitor — 5-minute cron health checks of site, API, and CMS with email alerts
- Backups — nightly D1 exports to R2 with a 60-snapshot rotation
Storage is D1 (SQLite at the edge, two databases per client), R2 for media and backups, and KV for caching. Forms are protected with Turnstile; outbound email goes through Resend, inbound through Cloudflare Email Routing.
Provisioning
A generator script reads one config object — project slug, account, keys —
and provisions everything with wrangler: D1 databases, R2 buckets, KV
namespaces, secrets (preview signing key, Payload secret), and the five
workers. Resource naming is enforced by convention: the frontend worker name
equals the project slug, and the CMS worker name doubles as the D1 database
and R2 bucket name.
one config file
-> wrangler provisions D1 x2, R2 x2, KV x2
-> deploys frontend, api, cms, monitor, backups
-> client is live with monitoring and nightly backups
Production Details
Patterns worth stealing
- Stale-on-outage SSR cache: the site keeps serving if the CMS is down
- Service bindings instead of public APIs: first-party cookies, no CORS
- Signed HMAC draft previews for editors
- Sitemap, robots.txt, OG tags, and redirect collections generated per client
- Form builder with submissions stored in D1
Lessons Learned
- Service bindings change the security model — with no public API surface, entire classes of CORS and token-leak problems disappear.
- Workers constraints shape the design — the CMS bundle limit forced a disciplined dependency diet on Payload.
- Conventions beat configuration — deriving resource names from one slug removed a whole category of copy-paste provisioning mistakes.