Routing and the backend proxy
The frontend server answers each path with one owner, whoever asks and whatever the method. It looks for the owner in this order.
Who answers a path
The renderer
Its icon endpoint (/api/_dms/icons), Vite's URLs, the built assets, and any file it has: a development source, a built asset, a module's public/ file.
In production, a path with a segment starting with a dot is never a built asset. Vite's build metadata under dist/client/.vite/ stays private, including its compressed copies. A leading /.well-known/ directory is the exception: its public files remain served.
The module routes
The routes of the modules' dms.server.ts, for every method. A method the module does not declare on its path answers 405, and a HEAD reaches the GET route. See Server routes.
The backend proxies
The prefixes the modules declare. The renderer proxies no path on its own: the DMS declares /api/** and /dms/** in its dms.server.ts.
The pages
Everything else is a page. A method other than GET and HEAD there is a 404. A backend route outside every declared prefix, such as a page's <page>/pagelayout, is never reached from the browser.
The server lists the result when it starts:
Routing: renderer files, then 21 module routes, then the backend proxies below, then the pages
/api/** -> backend (session), declared by antelopejs__dms-frontend-vue
/dms/** -> backend (session), declared by antelopejs__dms-frontend-vue
/ai/** -> backend (session), declared by @antelopejs/dms-ai-frontend-vue
/i18n/** -> backend (none), declared by @antelopejs/dms-lang-frontend-vue
/media/** -> backend (session), declared by @antelopejs/dms-media-frontend-vue
In development, every answer names its owner in an x-dms-route-owner header: renderer, module:<id>, backend:<pattern>, page or not-found.
Declaring a backend prefix
In dms.server.ts:
server.registerBackendProxy("/api/**"); // auth: "session", the default
server.registerBackendProxy("/media/**", { auth: "none" });
In the manifest, next to its publicEndpoints:
await AddFrontendModule({
// …
backendProxies: [{ path: "/webhooks/acme/**", auth: "none" }],
});
DMS_BACKEND_PROXIES adds entries of the manifest's shape, as a JSON array:
DMS_BACKEND_PROXIES=[{"path":"/webhooks/acme/**","auth":"none"}]
auth: "session"adds the session's access token to the request (Authorization: Bearer), server-side: the token never reaches the browser, which only holds the sealed,HttpOnlysession cookie. A request with a state-changing method must carry the frontend's ownOrigin, except on a public endpoint.auth: "none"never sends the session, so it needs noOrigin: for media, or a webhook that authenticates its caller itself.
A pattern is /x, /x/* (one segment below) or /x/** (the path and everything below), its first segment literal, outside the renderer's own paths. Of two patterns covering a path, the more specific wins. For one pattern, the highest-priority frontend module's declaration wins, then the manifest's, with a warning.
backendProxies reach the frontend only when it is fetched with the bootstrap credential. A build made without DMS_BOOTSTRAP_SECRET serves only the prefixes the modules' dms.server.ts declare.What the proxy sends
Every proxied request relays:
If-Match, so a backend route can refuse a write made from a stale copy (412or409);- the client's
OriginandRefereras received, so a public route can check where a beacon comes from; X-Forwarded-For,X-Forwarded-ProtoandX-Forwarded-Host, set by the proxy itself so a route can limit each client. The client's ownX-Forwarded-Foris extended with the socket's address only behindDMS_TRUSTED_PROXY_HOPStrusted proxies, and replaced by it otherwise.
The proxy matches the request's normalized path, the one it sends the backend, so .., %2e%2e or \ cannot reach another route through a declared prefix.
Response compression
The proxy asks the backend for an uncompressed answer. It compresses eligible text (JSON, text, XML and SVG) of at least 1 KiB, or of unknown length, using Brotli or gzip according to the client's Accept-Encoding qualities. Streaming compression delivers each part as the backend writes it. Eligible answers include Vary: Accept-Encoding even when the client receives identity bytes.
The proxy does not compress event streams, ranges, answers marked no-transform, or opaque encoded bodies. If the backend ignores the identity request, native fetch decodes supported encodings; the proxy removes the stale encoding and length before relaying or recompressing that body. A decoded encoded range returns 502 rather than forwarding byte offsets that no longer describe its body. HEAD and bodyless responses retain their representation metadata.
Workspaces and cache
How the CLI keys a workspace on a backend URL or a project, what a workspace holds, and when the cached manifest is used.
Public endpoints
Let callers outside the dashboard, such as a provider's webhook, reach a backend route through the frontend server without a session or an Origin.