@cio/tenant-router
Cloudflare Worker that fronts every browser-facing ClassroomIO host:
*.classroomio.school/*— free-tier tenant sitesclassroomio.school/*— apex (301 →classroomio.com)app.classroomio.com/*— admin dashboard- BYOD customer domains via Cloudflare for SaaS
The Worker forwards each request to one of two Render services based on path:
| Path | Upstream |
|---|---|
/api/* |
API service (/api prefix stripped) |
| anything else | Dashboard service |
It preserves the original host as X-Forwarded-Host so:
- The dashboard's SvelteKit adapter-node reads it via
HOST_HEADER=x-forwarded-hostandevent.url.hostbecomes the tenant/admin/BYOD host. - Better Auth on the API emits session cookies without a
Domainattribute (seepackages/db/src/auth.ts—crossSubDomainCookies: { enabled: false }), so the browser scopes each session to the exact host it called. Tenants onacme.classroomio.school, the admin app onapp.classroomio.com, and BYODlearn.acme.comeach get host-only sessions.
Deploy
pnpm --filter @cio/tenant-router deploy
Live tail:
pnpm --filter @cio/tenant-router tail
Env vars (wrangler.toml [vars])
| Var | Value |
|---|---|
DASHBOARD_UPSTREAM_HOST |
.onrender.com host of the dashboard service (no scheme) |
API_UPSTREAM_HOST |
.onrender.com host of the API service |
APEX_REDIRECT_TARGET |
URL to redirect classroomio.school apex to (default https://classroomio.com) |
Update the values in wrangler.toml after looking them up in the Render dashboard.
Cloudflare DNS expected setup
classroomio.school zone (new):
*CNAME → any valid target, Proxied@CNAME → any valid target, Proxied (Worker serves the 301)cnameCNAME → any valid target, Proxied — this is the value BYOD customers point their CNAME at
classroomio.com zone:
appCNAME → Proxied so the route bindsapiCNAME → DNS only (server-to-server callers and webhooks bypass the Worker)- The old wildcard
*CNAME onclassroomio.comshould be deleted — tenant traffic now belongs onclassroomio.school.
Cloudflare for SaaS (BYOD)
On the classroomio.school zone:
- SSL/TLS → Custom Hostnames: enabled
- Fallback Origin:
cname.classroomio.school— BYOD traffic lands on the Worker route and is forwarded through the same code path as*.classroomio.school.
The API uses these env vars (apps/api/src/services/org/domain.ts):
CLOUDFLARE_CUSTOM_HOSTNAMES_API_TOKEN— Cloudflare API token scoped to theclassroomio.schoolzone withZone → SSL and Certificates → Edit+Zone → Zone → Read.CLOUDFLARE_CUSTOM_HOSTNAMES_ZONE_ID— Zone ID forclassroomio.school.CLOUDFLARE_CUSTOM_HOSTNAME_CNAME_TARGET=cname.classroomio.school.