Skip to content

Replace explicit client route registration with SPA catch-all fallback#22136

Open
dannon wants to merge 3 commits intogalaxyproject:devfrom
dannon:fix/spa-catch-all
Open

Replace explicit client route registration with SPA catch-all fallback#22136
dannon wants to merge 3 commits intogalaxyproject:devfrom
dannon:fix/spa-catch-all

Conversation

@dannon
Copy link
Member

@dannon dannon commented Mar 16, 2026

Summary

Galaxy maintained 133 explicit add_client_route() calls in buildapp.py that had to stay in sync with the Vue router — forget one and the route 404s on page refresh. Since all client routes serve the same static js-app.mako template with no server-side data injection, there's no reason to enumerate them individually.

I replaced this with a simple catch-all: when no server route matches a non-API path, the WSGI layer falls back to serving the SPA directly and lets the Vue router handle routing. This includes showing its own 404 for truly invalid paths, which is better UX than a raw server 404 anyway.

What stays safe:

  • API routes still 404 normally (FastAPI handles them first, and the catch-all explicitly excludes /api/*)
  • Static files are served by URLMap before the WSGI app sees the request
  • Legacy controller routes (/authnz/*, /datasets/*, /u/{username}/*, etc.) match via the mapper before the fallback
  • Prefix mounting works the same as before

The only behavioral change is that previously-unregistered paths like /asdfgh now serve the SPA (where Vue shows its 404 page) instead of a raw server 404. New Vue routes no longer need a corresponding buildapp.py entry.

Came out of #22107 where Marius asked about per-route X-Frame-Options — that's not feasible because /published/* are client-side SPA routes, not FastAPI routes. But it surfaced this cleanup opportunity, and I also added conditional X-Frame-Options handling: published embed views (/published/*?embed=true) now skip the X-Frame-Options: SAMEORIGIN header so they can actually load in cross-origin iframes as intended (#21074). The exemption is applied in both the WSGI transaction init and the FastAPI middleware. Normal (non-embed) requests keep the header for clickjacking protection.

Test plan

  • Unit tests pass (test_framework_base, test_routes)
  • Playwright E2E tests pass (test_navigates_galaxy, test_histories_published, test_login, test_histories_list, test_published_pages)
  • API tests pass (test_framework — includes new X-Frame-Options tests for embed/non-embed published routes)
  • Pre-commit hooks pass (black, ruff, flake8, prettier)

@jmchilton
Copy link
Member

Such a nice developer QOL improvement - awesome!

@mvdbeek
Copy link
Member

mvdbeek commented Mar 17, 2026

It looks like there's just no request handling getting triggered at all ?

dannon added 3 commits March 18, 2026 18:14
Galaxy maintained 133 explicit add_client_route() calls in buildapp.py that had to stay in sync with the Vue router -- forget one and the route 404s on page refresh. Since all client routes serve the same static js-app.mako template with no server-side data injection, there's no reason to enumerate them.

Instead, when no server route matches a non-API path, the WSGI layer now falls back to serving the SPA directly and lets the Vue router handle routing (including showing its own 404 for truly invalid paths). API paths still 404 normally, static files are handled by URLMap before this code runs, and legacy controller routes still match via the mapper first.

This removes the clientside_routes mapper, the add_client_route() method, and all 133 route registrations from buildapp.py.
Published pages and workflows with embed=true are meant for cross-origin
iframe embedding, but the global X-Frame-Options: SAMEORIGIN header blocks
that. Conditionally skip the header when the request path starts with
/published/ and the embed=true query param is present. Applied to both the
WSGI transaction init and the FastAPI middleware for consistency.
The health check was doing GET / and expecting 200, but with the SPA
catch-all the root URL may not return 200 when the client build is
skipped. Using /api/version instead is more reliable since it works
regardless of client build state.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

3 participants