From bd02bbb88ae52f01e9a3fb76cfb3a80f1b6cc3ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:45:30 +0000 Subject: [PATCH 1/3] Initial plan From aa8231eca5bf2f52a44610b38ca48f75a8c5167a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:49:36 +0000 Subject: [PATCH 2/3] Fix SPA routing for multiuser mode by handling 404s in NoCacheStaticFiles Co-authored-by: lstein <111189+lstein@users.noreply.github.com> --- invokeai/app/api/no_cache_staticfiles.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/invokeai/app/api/no_cache_staticfiles.py b/invokeai/app/api/no_cache_staticfiles.py index 15a53270f1d..7d25faba65e 100644 --- a/invokeai/app/api/no_cache_staticfiles.py +++ b/invokeai/app/api/no_cache_staticfiles.py @@ -1,5 +1,6 @@ from typing import Any +from starlette.exceptions import HTTPException from starlette.responses import Response from starlette.staticfiles import StaticFiles @@ -12,6 +13,10 @@ class NoCacheStaticFiles(StaticFiles): Static files include the javascript bundles, fonts, locales, and some images. Generated images are not included, as they are served by a router. + + This class also implements proper SPA (Single Page Application) routing by serving index.html + for any routes that don't match static files, enabling client-side routing to work correctly + in production builds. """ def __init__(self, *args: Any, **kwargs: Any): @@ -26,3 +31,19 @@ def file_response(self, *args: Any, **kwargs: Any) -> Response: resp.headers.setdefault("Pragma", self.pragma) resp.headers.setdefault("Expires", self.expires) return resp + + async def get_response(self, path: str, scope: Any) -> Response: + """ + Override get_response to implement SPA routing. + + When a file is not found and html mode is enabled, serve index.html instead of raising a 404. + This allows client-side routing to work correctly in SPAs. + """ + try: + return await super().get_response(path, scope) + except HTTPException as exc: + # If the file is not found (404) and html mode is enabled, serve index.html + # This allows client-side routing to handle the path + if exc.status_code == 404 and self.html: + return await super().get_response("index.html", scope) + raise From 040dffc3ba9f87812651d354c08f18150bccf38b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:50:06 +0000 Subject: [PATCH 3/3] Improve type hint for scope parameter to use Scope type from starlette.types Co-authored-by: lstein <111189+lstein@users.noreply.github.com> --- invokeai/app/api/no_cache_staticfiles.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/invokeai/app/api/no_cache_staticfiles.py b/invokeai/app/api/no_cache_staticfiles.py index 7d25faba65e..6b3b172ba07 100644 --- a/invokeai/app/api/no_cache_staticfiles.py +++ b/invokeai/app/api/no_cache_staticfiles.py @@ -3,6 +3,7 @@ from starlette.exceptions import HTTPException from starlette.responses import Response from starlette.staticfiles import StaticFiles +from starlette.types import Scope class NoCacheStaticFiles(StaticFiles): @@ -32,7 +33,7 @@ def file_response(self, *args: Any, **kwargs: Any) -> Response: resp.headers.setdefault("Expires", self.expires) return resp - async def get_response(self, path: str, scope: Any) -> Response: + async def get_response(self, path: str, scope: Scope) -> Response: """ Override get_response to implement SPA routing.