Multi-Domain Architecture
Serve CMS, Account UI, and Admin on separate custom domains from a single Cloudflare Worker.
A single Quickback-compiled Cloudflare Worker can serve your API, CMS, Account UI, and Admin panel on separate custom domains. The compiler generates hostname-based routing so each domain serves the right content.
How It Works
When you configure custom domains for CMS, Account, or Admin, the compiler:
- Adds
custom_domainroutes towrangler.toml - Generates hostname-based middleware in the Worker
- Auto-configures cross-subdomain cookies for shared authentication
- Auto-infers a unified
quickback.{baseDomain}fallback domain
All five domains point to the same Worker — there's only one deployment.
Domain Types
| Domain | Config | Serves | API Routes |
|---|---|---|---|
api.example.com | providers.runtime.config.routes | API only | All |
cms.example.com | cms: { domain } | CMS SPA | Blocked (404) |
auth.example.com | account: { domain } | Account SPA | Pass-through |
admin.example.com | account: { adminDomain } | Account SPA | Pass-through |
quickback.example.com | Auto-inferred | Everything | All |
CMS Domain
Serves the CMS SPA at root (/). API routes (/api/, /auth/, /admin/, /storage/, /health) return 404 — preventing direct API access on the CMS domain.
Account Domain
Serves the Account SPA at root (/). API and auth routes pass through to the Worker so authentication flows work directly on this domain.
Admin Domain
Identical behavior to the Account domain — serves the same Account SPA at root. The Account SPA's client-side router handles the /admin route, restricting access to users with the admin role.
Unified Domain
The compiler auto-infers a quickback.{baseDomain} domain from your configured custom domains. For example, if you have cms.example.com, it creates quickback.example.com.
On this domain, everything is available on a single origin:
| Path | Content |
|---|---|
/ | Redirects to /cms/ |
/cms/ | CMS SPA |
/account/ | Account SPA |
/api/v1/... | API routes |
/auth/... | Auth routes |
/admin/... | Admin API routes |
/storage/... | File storage |
/health | Health check |
/openapi.json | OpenAPI spec |
The unified domain is useful for development and debugging. You can override it with an explicit domain field in your config.
Configuration
export default {
name: "my-app",
template: "hono",
cms: { domain: "cms.example.com", access: "admin" },
account: {
domain: "auth.example.com",
adminDomain: "admin.example.com",
name: "My App",
auth: { password: true, admin: true },
},
trustedOrigins: [
"https://auth.example.com",
"https://admin.example.com",
"https://cms.example.com",
],
providers: {
runtime: { name: "cloudflare", config: {
routes: [{ pattern: "api.example.com", custom_domain: true }],
}},
// ...database, auth
},
};This generates the following wrangler.toml routes:
routes = [
{ pattern = "api.example.com", custom_domain = true },
{ pattern = "cms.example.com", custom_domain = true },
{ pattern = "auth.example.com", custom_domain = true },
{ pattern = "admin.example.com", custom_domain = true },
{ pattern = "quickback.example.com", custom_domain = true }
]Cross-Subdomain Authentication
When two or more custom domains share a parent domain (e.g., cms.example.com + auth.example.com), the compiler automatically configures Better Auth for cross-subdomain cookie sharing:
- Sets
crossSubDomainCookies: { enabled: true, domain: '.example.com' } - Sets
sameSite: 'none'andsecure: trueon auth cookies
This means a user who logs in on auth.example.com is automatically authenticated on cms.example.com and admin.example.com — no additional setup needed.
Automatic Detection
Cross-subdomain cookies are configured automatically. You only need to set them manually if your domains don't share a common parent (e.g., auth.myapp.com + cms.different.com).
Compile-Time Feature Gating
When the compiler builds the Account SPA, it excludes route files for disabled features before the Vite build. This means disabled features never appear in the JavaScript bundle.
| Feature Flag | Routes Excluded When false |
|---|---|
auth.organizations | Dashboard, organization CRUD, org switching ($slug) |
auth.passkey | Passkey management and setup |
auth.admin | Admin panel routes |
This keeps bundle sizes minimal and prevents dead code in production. Users can't access disabled features even if they navigate to the URL directly — the routes don't exist in the build.
Hostname Routing Details
The compiler generates middleware that checks new URL(c.req.url).hostname on every request:
CMS domain — Blocks API routes with 404. All other paths are mapped to the /cms/ asset prefix and served with SPA fallback to /cms/index.html.
Account/Admin domain — API and auth routes (/api/, /auth/, /admin/, /storage/, /health) pass through to Hono handlers. All other paths are mapped to the /account/ asset prefix and served with SPA fallback to /account/index.html.
Unified domain — No hostname filtering. CMS is served at /cms/, Account at /account/. Root (/) redirects to /cms/. API routes work at their standard paths.
Without Custom Domains
If you enable cms and account without custom domains, everything is served on a single domain:
- CMS at
/cms/(root/redirects to/cms/) - Account UI at
/account/ - API at
/api/v1/ - Auth at
/auth/
This is the simplest setup — no DNS configuration needed, no cross-subdomain cookies. Auth cookies work naturally because everything is same-origin.
See Also
- Configuration — CMS and Account config options
- Output Structure — Generated file and directory layout
- CMS — Schema-driven admin interface
- Account UI — Authentication and account management