Quickback Docs

Multi-Domain Architecture

Serve CMS, Account UI, Admin, and user-authored SPAs on separate custom domains from a single Cloudflare Worker.

A single Quickback-compiled Cloudflare Worker can serve your API, CMS, Account UI, Admin panel, and your own product SPAs 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, Admin, or apps, the compiler:

  1. Adds custom_domain routes to wrangler.toml
  2. Generates hostname-based middleware in the Worker
  3. Auto-configures cross-subdomain cookies for shared authentication
  4. Auto-infers a unified quickback.{baseDomain} fallback domain

All configured domains point to the same Worker — there's only one deployment.

Domain Types

DomainConfigServesAPI Routes
api.example.comproviders.runtime.config.routesAPI onlyAll
cms.example.comcms: { domain }CMS SPABlocked (404)
auth.example.comaccount: { domain }Account SPAPass-through
admin.example.comaccount: { adminDomain }Account SPAPass-through
www.example.comapps: { <name>: { domain } }User-authored SPAPass-through
quickback.example.comAuto-inferredEverythingAll

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.

App Domains

Mount your own product SPAs on dedicated hostnames via the apps field. Each entry binds one or more hostnames to a directory under src/apps/<name>/ and inherits the same API/auth pass-through behavior as the Account domain — so the SPA can hit /api/v1/... and /auth/... same-origin without CORS.

apps: {
  www: {
    domain: "www.example.com",
    aliasDomains: ["example.com"], // apex serves the same SPA
  },
  marketing: {
    domain: "marketing.example.com",
  },
}
OptionRequiredDescription
domainYesPrimary custom hostname served at root (/)
aliasDomainsNoAdditional hostnames that serve the same SPA. Each gets its own custom_domain = true route. Common case: apex (example.com) alongside www.example.com.
assetsDirNoSubdirectory under src/apps/ to serve from. Defaults to the record key. Override only when the on-disk directory needs to differ from the URL-facing name.

Drop the prebuilt SPA output at quickback/apps/<name>/ — the source-apps passthrough copies it byte-for-byte into src/apps/<name>/ on every compile. The compiler doesn't build the SPA; it just emits routing for whatever's already there. See Output Structure for the passthrough rules.

The names cms, account, and public are reserved (they collide with compiler-emitted directories under src/apps/). Two app entries can't claim the same hostname, and an app's hostname can't collide with cms.domain / account.domain / account.adminDomain.

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:

PathContent
/Redirects to /cms/
/cms/CMS SPA
/account/Account SPA
/api/v1/...API routes
/auth/...Auth routes
/admin/...Admin API routes
/storage/...File storage
/healthHealth check
/openapi.jsonOpenAPI spec

The unified domain is useful for development and debugging. You can override it with an explicit domain field in your config.

Configuration

quickback/quickback.config.ts
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 },
  },
  apps: {
    www: {
      domain: "www.example.com",
      aliasDomains: ["example.com"],
    },
  },
  providers: {
    runtime: { name: "cloudflare", config: {
      routes: [{ pattern: "api.example.com", custom_domain: true }],
    }},
    // ...database, auth
  },
};

trustedOrigins is optional here — every cms.domain, account.domain, account.adminDomain, and apps[*].domain (plus aliases) is automatically added. Declare trustedOrigins only when you need to allow an origin the compiler can't infer (e.g. a third-party domain).

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 = "www.example.com", custom_domain = true },
  { pattern = "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 + www.example.com), the compiler automatically configures Better Auth for cross-subdomain cookie sharing. App hostnames (primary + aliases) are folded into the shared-parent detection alongside the CMS, Account, Admin, and top-level domain slots:

  • Sets crossSubDomainCookies: { enabled: true, domain: '.example.com' }
  • Sets sameSite: 'none' and secure: true on auth cookies

This means a user who logs in on auth.example.com is automatically authenticated on cms.example.com, admin.example.com, and www.example.com — no additional setup needed. Apex aliases (e.g. example.com listed under aliasDomains) are recognized as already being the parent — they don't break shared-parent equality.

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 FlagRoutes Excluded When false
auth.organizationsDashboard, organization CRUD, org switching ($slug)
auth.passkeyPasskey management and setup
auth.adminAdmin 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.

App domain — Identical to Account: API/auth/admin/storage/health pass through, other paths map to /<assetsDir>/ with SPA fallback. The multi-domain catchall (emitted when both CMS and Account are configured) explicitly skips app hostnames, so an API miss on an app host lands in app.notFound (404 JSON) instead of stray-serving a sibling SPA's 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.

Preview Deploys

The same compiled bundle can be deployed to additional hostnames — preview environments, staging, per-PR URLs — without recompiling. Set the EXTRA_APP_HOSTS env var on the deploy target (a comma-separated list of hostnames) and the worker treats each one like an entry in apps[*].domain:

  • Apps hostname middleware serves the SPA at root.
  • CORS / Better Auth trustedOrigins trusts requests whose Origin hostname matches.

Empty or unset means no extension and zero behavior change for production.

wrangler.preview.toml
[vars]
EXTRA_APP_HOSTS = "app-preview.example.com,pr-42.example.com"
# Or set as a secret per deploy target
wrangler secret put EXTRA_APP_HOSTS --env preview

Mirrors the existing BETTER_AUTH_URL auto-trust behavior, where the deployed worker auto-trusts its own configured base origin without needing an explicit trustedOrigins entry. With EXTRA_APP_HOSTS, one env var extends both the apps allowlist and the CORS allowlist.

Multi-app deploys

When more than one app is hostname-mounted, extras attach to the first registered apps middleware (Hono runs middleware in registration order and terminates on first match). If you need extras to route to a specific app in a multi-app deploy, set apps[*].domain directly and recompile rather than using this env var.

See Also

On this page