From 720144627e819707721f05552f774db254a8ada7 Mon Sep 17 00:00:00 2001 From: Joerg Date: Sun, 18 Jan 2026 07:17:28 +0100 Subject: [PATCH] fix: return proper HTML for SPA routes instead of Bun error page When accessing client-side routes (like /qsos) via curl or non-JS clients, the server attempted to open them as static files, causing Bun to throw an unhandled ENOENT error that showed an ugly error page. Now checks if a path has a file extension before attempting to serve it. Paths without extensions are immediately served index.html for SPA routing. Also improves the 503 error page with user-friendly HTML when frontend build is missing. Co-Authored-By: Claude Sonnet 4.5 --- src/backend/index.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/backend/index.js b/src/backend/index.js index 8bb25da..fad1b15 100644 --- a/src/backend/index.js +++ b/src/backend/index.js @@ -680,13 +680,28 @@ const app = new Elysia() try { const fullPath = `src/frontend/build${filePath}`; - // Use Bun.file() which doesn't throw for non-existent files + // For paths without extensions or directories, use SPA fallback immediately + // This prevents errors when trying to open directories as files + const ext = filePath.split('.').pop(); + const hasExtension = ext !== filePath && ext.length <= 5; // Simple check for file extension + + if (!hasExtension) { + // No extension means it's a route, not a file - serve index.html + const indexFile = Bun.file('src/frontend/build/index.html'); + return new Response(indexFile, { + headers: { + 'Content-Type': 'text/html; charset=utf-8', + 'Cache-Control': 'no-cache, no-store, must-revalidate', + }, + }); + } + + // Try to serve actual files (with extensions) const file = Bun.file(fullPath); const exists = file.exists(); if (exists) { // Determine content type - const ext = filePath.split('.').pop(); const contentTypes = { 'js': 'application/javascript', 'css': 'text/css', @@ -719,6 +734,7 @@ const app = new Elysia() } } catch (err) { // File not found or error, fall through to SPA fallback + logger.debug('Error serving static file, falling back to SPA', { path: pathname, error: err.message }); } // SPA fallback - serve index.html for all other routes @@ -730,8 +746,16 @@ const app = new Elysia() 'Cache-Control': 'no-cache, no-store, must-revalidate', }, }); - } catch { - return new Response('Frontend not built. Run `bun run build`', { status: 503 }); + } catch (err) { + logger.error('Frontend build not found', { error: err.message }); + return new Response( + 'Quickawards - Unavailable' + + '' + + '

Service Temporarily Unavailable

' + + '

The frontend application is not currently available. This usually means the application is being updated or restarted.

' + + '

Please try refreshing the page in a few moments.

', + { status: 503, headers: { 'Content-Type': 'text/html; charset=utf-8' } } + ); } })