Back to Projects

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.

Cloudflare Workers
Astro
React 19
Hono
Payload CMS
OpenNext
D1
R2
KV
Tailwind CSS 4
TypeScript
Turnstile
Resend

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

  1. Service bindings change the security model — with no public API surface, entire classes of CORS and token-leak problems disappear.
  2. Workers constraints shape the design — the CMS bundle limit forced a disciplined dependency diet on Payload.
  3. Conventions beat configuration — deriving resource names from one slug removed a whole category of copy-paste provisioning mistakes.