# Multi-Domain Architecture

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

| 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 |
| `www.example.com` | `apps: { <name>: { domain } }` | User-authored 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.

### 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.

```typescript
apps: {
  www: {
    domain: "www.example.com",
    aliasDomains: ["example.com"], // apex serves the same SPA
  },
  marketing: {
    domain: "marketing.example.com",
  },
}
```

| Option | Required | Description |
|--------|----------|-------------|
| `domain` | Yes | Primary custom hostname served at root (`/`) |
| `aliasDomains` | No | Additional hostnames that serve the same SPA. Each gets its own `custom_domain = true` route. Common case: apex (`example.com`) alongside `www.example.com`. |
| `assetsDir` | No | Subdirectory 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](/compiler/config/output#source-apps-quickbackapps) 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:

| 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

```typescript title="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:

```toml
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 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`.

**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.

```toml title="wrangler.preview.toml"
[vars]
EXTRA_APP_HOSTS = "app-preview.example.com,pr-42.example.com"
```

```bash
# 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

- [Configuration](/compiler/config) — CMS and Account config options
- [Output Structure](/compiler/config/output) — Generated file and directory layout
- [CMS](/stack/cms) — Schema-driven admin interface
- [Account UI](/stack/account-ui) — Authentication and account management
