Storefront render
Public merchant storefronts are served by one shared Next.js app (web), not
by a server inside each box. The render app resolves every incoming host to the
correct tenant and the correct bank engine backend, then renders the
storefront against that backend’s data.
Why centralized render
- One deployment, every bank. Adding a box does not require standing up a new storefront server.
- Control-plane host routing. The control plane (via DNS delegation) maps each public host to its box; the render app asks it which backend serves a host.
- Per-bank isolation. Each request fetches only from its own box’s engine, threaded through request headers — never a shared data pool.
End-to-end flow
- Request arrives at the edge for some host.
- Middleware resolves the host to a tenant slug and a backend URL — by platform subdomain, by control-plane lookup for non-platform domains, or by custom-domain lookup. See Host & tenant routing.
- Headers are injected (
x-subdomain-tenant,x-box-backend-url) and the request is rewritten to the[tenant]route. - Route handlers fetch tenant data; the API client reads
x-box-backend-urlso every consumer talks to the right box with no call-site changes. - Storefront API calls are forwarded through the proxy route with security filters.
Host resolution
For non-platform domains the render app calls the planned control-plane endpoint
GET /boxes/resolve?host=… to learn the backend URL and match kind.
⚠️
GET /boxes/resolve is not yet implemented in the control plane. Until it
ships, box-resolve.ts in the render app acts as a client stub. Positive
lookups are cached 60s, negative lookups 30s (with a 2s timeout), so an
unclaimed host cannot stampede the backends.
Sections
- Host & tenant routing — the full resolution order and header injection.
- Proxy & configuration — the storefront API proxy, allowed prefixes, and env vars.