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 <noreply@anthropic.com>
This commit is contained in:
@@ -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(
|
||||
'<!DOCTYPE html><html><head><title>Quickawards - Unavailable</title></head>' +
|
||||
'<body style="font-family: system-ui; max-width: 600px; margin: 100px auto; padding: 20px;">' +
|
||||
'<h1>Service Temporarily Unavailable</h1>' +
|
||||
'<p>The frontend application is not currently available. This usually means the application is being updated or restarted.</p>' +
|
||||
'<p>Please try refreshing the page in a few moments.</p></body></html>',
|
||||
{ status: 503, headers: { 'Content-Type': 'text/html; charset=utf-8' } }
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user