Fix SPA routing — page refresh returns 404 #33

Closed
opened 2026-03-18 10:39:26 -04:00 by connor · 0 comments
Owner

The Yew frontend uses client-side routing via yew-router with BrowserRouter. This uses the History API to manage URLs like /search, /library, /artists/5, etc. The problem: when the user navigates to a page (e.g., /search) and then refreshes the browser, the Actix backend receives a request for /search and tries to serve it as a static file — which doesn't exist. The result is a 404 or a blank page.

This is a standard SPA routing issue. The fix is a server-side fallback: any request that doesn't match an /api/* route and doesn't match a static file should return index.html, letting the Yew router handle the path client-side.

Implementation

In shanty-web/src/main.rs, the static file serving needs to be configured with a fallback. Actix-files doesn't have a built-in SPA fallback, so the approach is:

  1. Add a catch-all handler that serves index.html for any non-API, non-file request
  2. This handler should be registered AFTER the API routes and static files service
  3. The handler reads static/index.html and returns it with text/html content type

Alternatively, use actix_files::NamedFile as a default handler:

async fn spa_fallback() -> actix_web::Result<actix_files::NamedFile> {
    Ok(actix_files::NamedFile::open("static/index.html")?)
}
// Register as .default_service(web::route().to(spa_fallback))

Acceptance Criteria

  • Navigating to /search and refreshing the page loads the search page correctly
  • All SPA routes work on direct navigation / refresh (/library, /artists/5, /downloads, /settings)
  • API routes (/api/*) still work normally and don't get caught by the fallback
  • Static assets (.js, .wasm, .css) are still served correctly

Dependencies

  • shanty-web backend (the fix is server-side)
The Yew frontend uses client-side routing via `yew-router` with `BrowserRouter`. This uses the History API to manage URLs like `/search`, `/library`, `/artists/5`, etc. The problem: when the user navigates to a page (e.g., `/search`) and then refreshes the browser, the Actix backend receives a request for `/search` and tries to serve it as a static file — which doesn't exist. The result is a 404 or a blank page. This is a standard SPA routing issue. The fix is a server-side fallback: any request that doesn't match an `/api/*` route and doesn't match a static file should return `index.html`, letting the Yew router handle the path client-side. ### Implementation In `shanty-web/src/main.rs`, the static file serving needs to be configured with a fallback. Actix-files doesn't have a built-in SPA fallback, so the approach is: 1. Add a catch-all handler that serves `index.html` for any non-API, non-file request 2. This handler should be registered AFTER the API routes and static files service 3. The handler reads `static/index.html` and returns it with `text/html` content type Alternatively, use `actix_files::NamedFile` as a default handler: ```rust async fn spa_fallback() -> actix_web::Result<actix_files::NamedFile> { Ok(actix_files::NamedFile::open("static/index.html")?) } // Register as .default_service(web::route().to(spa_fallback)) ``` ### Acceptance Criteria - [ ] Navigating to `/search` and refreshing the page loads the search page correctly - [ ] All SPA routes work on direct navigation / refresh (`/library`, `/artists/5`, `/downloads`, `/settings`) - [ ] API routes (`/api/*`) still work normally and don't get caught by the fallback - [ ] Static assets (`.js`, `.wasm`, `.css`) are still served correctly ### Dependencies - shanty-web backend (the fix is server-side)
connor added the HighPriorityMVP labels 2026-03-18 10:42:36 -04:00
connor started working 2026-03-18 10:45:33 -04:00
connor worked for 10 minutes 2026-03-18 10:55:40 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Total Time Spent: 10 minutes
connor
10 minutes
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Shanty/Main#33