Storefront renderHost & tenant routing

Host & tenant routing

The render middleware turns an arbitrary public host into two facts: which tenant the request is for, and which engine backend serves it.

Resolution order

The middleware tries these in order and stops at the first match:

  1. Platform subdomain. A host shaped like {tenant}.{platform-host} yields the slug directly.
  2. Non-platform domain. Call resolveBoxHost(host) against the control plane:
    • match: 'wildcard' with a tenantSlug → use that slug and the box’s backendUrl.
    • match: 'exact' → ask the bank backend GET {backendUrl}/tenants/by-domain/{host} for the slug.
  3. Custom platform domain. Look up the slug via lookupCustomDomain(host).
  4. Fall through. No tenant; handled as a path-based or platform route.

Tenant resolution on the engine side

The engine resolves a tenant independently for direct API calls (tenant.middleware.ts):

  1. Honor the X-Tenant-Slug header (set by the render proxy).
  2. Otherwise use the Host header: strip the port, try a custom-domain lookup, then a subdomain pattern match ({slug}.{platform}).

Only active or pending tenants resolve; anything else is treated as not found.

The @CurrentTenant() decorator throws 404 when no tenant is attached, and @TenantId() extracts the resolved tenant id for handlers.

Header injection

When a tenant is resolved, the middleware rewrites the request and threads headers downstream:

HeaderPurpose
x-subdomain-tenantThe resolved tenant slug
x-box-backend-urlThe bank engine backend the [tenant] routes fetch from
x-pb-preview-kindPreview mode (draft / saved) when applicable
Content-Security-Policyframe-ancestors allowing the dashboard to embed Live Preview

The API client reads x-box-backend-url from request headers, so every data fetch in the [tenant] route tree targets the correct box automatically — no per-call configuration.

Caching

LookupPositive TTLNegative TTLTimeout
resolveBoxHost60s30s2s
lookupTenantOnBackend60s30s2s

Negative caching is deliberate: an unclaimed or mistyped host is remembered as “no box” briefly so repeated requests cannot stampede the control plane or a bank backend.