Reloading or deep-linking any page but / returns 404: no SPA fallback #164

Closed
opened 2026-09-26 08:32:42 +00:00 by claude-bot · 2 comments
Collaborator

Bug

The built frontend is served with StaticFiles(html=True) (backend/app/main.py, around line 120). That serves index.html for / only. A direct load or reload of a client-side route (/settings, /remote, /audit-log) returns 404.

Why it matters now:

  • Webhook deep links (#162) had to be written as /?filter=…&date=… to work around it.
  • /remote (#24) is meant to be bookmarked on a phone, and a bookmark that 404s is not much of a remote.
  • The installable app (#154) needs its shortcuts to /remote and to the review queue to load directly.

Fix

  • Serve index.html for any GET that isn't an API, auth, metrics or static-asset path and doesn't match a file. Do this with a catch-all route registered after the API routers, or with a StaticFiles subclass that falls back on 404.
  • Never fall back for /api/*, /auth/* or /metrics. An unknown API path must stay a JSON 404, not return HTML with a 200.
  • Tests: /remote and /settings return index.html; /api/v1/does-not-exist is a JSON 404; a missing asset under /assets/ is still a 404.
  • E2E: reload on /remote.
## Bug The built frontend is served with `StaticFiles(html=True)` (`backend/app/main.py`, around line 120). That serves `index.html` for `/` only. A direct load or reload of a client-side route (`/settings`, `/remote`, `/audit-log`) returns **404**. Why it matters now: - Webhook deep links (#162) had to be written as `/?filter=…&date=…` to work around it. - `/remote` (#24) is meant to be bookmarked on a phone, and a bookmark that 404s is not much of a remote. - The installable app (#154) needs its shortcuts to `/remote` and to the review queue to load directly. ## Fix - **Serve `index.html` for any GET** that isn't an API, auth, metrics or static-asset path and doesn't match a file. Do this with a catch-all route registered *after* the API routers, or with a `StaticFiles` subclass that falls back on 404. - **Never fall back for `/api/*`, `/auth/*` or `/metrics`.** An unknown API path must stay a JSON 404, not return HTML with a 200. - **Tests:** `/remote` and `/settings` return `index.html`; `/api/v1/does-not-exist` is a JSON 404; a missing asset under `/assets/` is still a 404. - **E2E:** reload on `/remote`.
claude-bot added this to the v1.2.0 milestone 2026-09-26 08:32:56 +00:00
Author
Collaborator

Picking this up now. It's small and separate from the rest, and #154 (the installable app) needs it.

Picking this up now. It's small and separate from the rest, and #154 (the installable app) needs it.
Author
Collaborator

Done in #170 (squash-merged, CI green). backend/app/spa.py's SPAStaticFiles falls back to index.html, with Cache-Control: no-cache, for client-side routes. Backend paths (/api/, /auth/, /metrics, /openapi.json) and missing assets (anything with a file extension, or under /assets/) still return a real 404. Covered by 11 backend tests and an E2E that loads /settings directly and then reloads it. This unblocks #154.

Done in #170 (squash-merged, CI green). `backend/app/spa.py`'s `SPAStaticFiles` falls back to `index.html`, with `Cache-Control: no-cache`, for client-side routes. Backend paths (`/api/`, `/auth/`, `/metrics`, `/openapi.json`) and missing assets (anything with a file extension, or under `/assets/`) still return a real 404. Covered by 11 backend tests and an E2E that loads `/settings` directly and then reloads it. This unblocks #154.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Iris-WLED#164
No description provided.