What
There is no app-writable route for an arbitrary static file. x dev's route table is a closed literal — apiRoutes, assetRoutes, storageRoutes, islandRoutes, styleRoutes, appRoutes (packages/cli/src/cmd-dev.ts:209-219) — with no app-supplied slot.
GET /icon.png -> 404 # apps/web/site/icon.png IS committed
GET /favicon.ico -> 200 # dedicated framework route
(measured against a running 20.1.0 app)
Why it matters beyond one app: the guard-rails point at a door that is not there
Together those say, correctly: self-host your fonts. But nothing can then serve a self-hosted font.
Escape hatches ruled out at source
runtime.ts middleware wraps a route that already matched; an unmatched path throws routeNotFound first (packages/http/src/stages.ts:173).
- An action cannot return a
Response — json(result) or redirect(...) only.
/media is gated on storage:read; /icons serves only the PWA matrix.
RouteMeta has no link field, so there is no <link rel="preload"> seam either.
The workaround we shipped (so you can judge how bad the gap is)
Both fonts base64-embedded as data: URIs inside the app stylesheet — the one app file the framework does serve — plus security.csp.extend: { 'font-src': ['data:'] }. It works (verified in Chrome: both faces loaded, no CSP errors) at the cost of +33% bytes, no independent caching, and no preload.
Suggested shapes, least invasive first
- A declared static directory per surface that joins the route table the way
styleRoutes does.
- An app-supplied route slot.
- A
fontFace seam on defineTheme() that emits the @font-face and the asset route together — which keeps the existing validator honest instead of worked around.
Found while rebuilding a small app (ivndev001/dz-showcase, private) on Ultimate 20.1.0; re-checked against main at 46aa5b9 (20.1.2) on 2026-09-17 before filing. Filed by an agent on the repository owner's instruction.
What
There is no app-writable route for an arbitrary static file.
x dev's route table is a closed literal —apiRoutes, assetRoutes, storageRoutes, islandRoutes, styleRoutes, appRoutes(packages/cli/src/cmd-dev.ts:209-219) — with no app-supplied slot.(measured against a running 20.1.0 app)
Why it matters beyond one app: the guard-rails point at a door that is not there
font-src 'self'—packages/http/src/security-headers.ts:64defineTheme()'s font validator admits family names only, so no@importor URL passes —packages/ui/src/theme/brand.ts:42-46Together those say, correctly: self-host your fonts. But nothing can then serve a self-hosted font.
Escape hatches ruled out at source
runtime.tsmiddleware wraps a route that already matched; an unmatched path throwsrouteNotFoundfirst (packages/http/src/stages.ts:173).Response—json(result)orredirect(...)only./mediais gated onstorage:read;/iconsserves only the PWA matrix.RouteMetahas no link field, so there is no<link rel="preload">seam either.The workaround we shipped (so you can judge how bad the gap is)
Both fonts base64-embedded as
data:URIs inside the app stylesheet — the one app file the framework does serve — plussecurity.csp.extend: { 'font-src': ['data:'] }. It works (verified in Chrome: both facesloaded, no CSP errors) at the cost of +33% bytes, no independent caching, and no preload.Suggested shapes, least invasive first
styleRoutesdoes.fontFaceseam ondefineTheme()that emits the@font-faceand the asset route together — which keeps the existing validator honest instead of worked around.Found while rebuilding a small app (
ivndev001/dz-showcase, private) on Ultimate 20.1.0; re-checked againstmainat46aa5b9(20.1.2) on 2026-09-17 before filing. Filed by an agent on the repository owner's instruction.