fix(server): add styled 404 page + stop SPA fallback serving stale HTML

Add app.notFound() handler with themed 404 page instead of Hono's
plain "404 Not Found". Restrict canvas.html SPA fallback to /rspace
sub-paths only — was serving index.html for all unmatched routes,
causing stale "Activated" page to appear on navigation errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-04-15 17:19:41 -04:00
parent 72587ef690
commit c82eca38fa
1 changed files with 36 additions and 5 deletions

View File

@ -3664,6 +3664,37 @@ app.get("/:space", (c) => {
return c.redirect("/", 302); return c.redirect("/", 302);
}); });
// ── Custom 404 page ──
app.notFound((c) => {
const path = c.req.path;
// API routes: return JSON 404
if (path.startsWith("/api/") || c.req.header("accept")?.includes("application/json")) {
return c.json({ error: "Not found" }, 404);
}
return c.html(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Page Not Found | rSpace</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui,-apple-system,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0f172a;color:#e2e8f0}
.c{max-width:480px;text-align:center;padding:2rem}
h1{font-size:4rem;margin-bottom:0.5rem;background:linear-gradient(135deg,#5eead4,#a78bfa);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
p{color:#94a3b8;line-height:1.6;margin-bottom:1.5rem}
a{color:#5eead4;text-decoration:none;padding:0.5rem 1.5rem;border:1px solid #5eead4;border-radius:8px;transition:all 0.2s}
a:hover{background:#5eead4;color:#0f172a}
.sub{font-size:0.85rem;color:#475569;margin-top:2rem}
</style>
</head>
<body><div class="c">
<h1>404</h1>
<p>This page doesn't exist it may have moved or the URL might be wrong.</p>
<a href="/">Back to rSpace</a>
<p class="sub">${path}</p>
</div></body></html>`, 404);
});
// ── WebSocket types ── // ── WebSocket types ──
interface WSData { interface WSData {
communitySlug: string; communitySlug: string;
@ -4343,11 +4374,11 @@ const server = Bun.serve<WSData>({
if (shellResponse.status === 200) return shellResponse; if (shellResponse.status === 200) return shellResponse;
} }
// Non-module path — try canvas/index SPA fallback // Canvas SPA fallback (only for /rspace sub-paths, not general 404s)
const canvasHtml = await serveStatic("canvas.html"); if (parts[0] === "rspace" || (subdomain && parts[0] === "rspace")) {
if (canvasHtml) return canvasHtml; const canvasHtml = await serveStatic("canvas.html");
const indexHtml = await serveStatic("index.html"); if (canvasHtml) return canvasHtml;
if (indexHtml) return indexHtml; }
} }
} }