diff --git a/src/langbot/pkg/api/http/controller/main.py b/src/langbot/pkg/api/http/controller/main.py index e0a78ea7..8e747777 100644 --- a/src/langbot/pkg/api/http/controller/main.py +++ b/src/langbot/pkg/api/http/controller/main.py @@ -105,23 +105,22 @@ class HTTPController: ): if os.path.exists(os.path.join(frontend_path, path + '.html')): path += '.html' - elif path.startswith('home/'): - # SPA fallback for /home/* sub-routes. - # Entity detail views use query params (e.g. /home/bots?id=uuid), - # so the pre-rendered list page is served directly via path + '.html'. - # This fallback handles any remaining unmatched sub-paths. - segments = path.rstrip('/').split('/') + elif not path.startswith('api/'): + # SPA fallback: serve index.html for all non-API, non-static routes + # so that React Router can handle client-side routing (Vite SPA). + # For /home/* sub-routes, first try parent .html files (pre-rendered pages). + if path.startswith('home/'): + segments = path.rstrip('/').split('/') + for i in range(len(segments) - 1, 0, -1): + parent_path = '/'.join(segments[:i]) + '.html' + if os.path.exists(os.path.join(frontend_path, parent_path)): + response = await quart.send_from_directory(frontend_path, parent_path, mimetype='text/html') + response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' + response.headers['Pragma'] = 'no-cache' + response.headers['Expires'] = '0' + return response - # Walk up parent segments looking for matching .html files - for i in range(len(segments) - 1, 0, -1): - parent_path = '/'.join(segments[:i]) + '.html' - if os.path.exists(os.path.join(frontend_path, parent_path)): - response = await quart.send_from_directory(frontend_path, parent_path, mimetype='text/html') - response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' - response.headers['Pragma'] = 'no-cache' - response.headers['Expires'] = '0' - return response - # Final fallback to index.html for /home/* routes + # Fallback to index.html for SPA client-side routing response = await quart.send_from_directory(frontend_path, 'index.html', mimetype='text/html') response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' response.headers['Pragma'] = 'no-cache'