-
-
Notifications
You must be signed in to change notification settings - Fork 386
Description
What version of Elysia is running?
1.4.13
What platform is your computer?
Linux 6.6.87.2-microsoft-standard-WSL2 x86_64 x86_64
What environment are you using
1.3.1
Are you using dynamic mode?
NO
What steps can reproduce the bug?
Goal: Implement a full-featured SPA with client-side routing following the https://elysiajs.com/patterns/fullstack-dev-server.html
Step 1: Try to return the Bun HTML bundle for all routes that are not defined as API endpoints using onError:
import index from '@public/index.html'
new Elysia()
.use(staticPlugin({ assets: "public", prefix: "/" }))
.mount("/api", auth.handler)
.onError(({ code, request }) => {
if (code === "NOT_FOUND") {
return index; // Attempt to return HTML bundle
}
})Step 2: Navigate to any non-existent route like /projects/123
What is the expected behavior?
The server should return the HTML bundle (served via Bun's native HTML bundler) for all unmatched routes, allowing the client-side router to handle navigation. This is the standard SPA fallback pattern.
Bun's Native Router already supports this feature out of the box, so Elysia should be able to leverage it when returning HTML files.
What do you see instead?
Problem 1: onError serializes HTML as JSON
When returning the HTML bundle from onError, Elysia attempts to serialize it as JSON instead of serving it as an HTML document. The browser receives JSON instead of rendered HTML.
Problem 2: Wildcard route get('/*') doesn't work with .use() or .mount()
The traditional solution of using a wildcard route also fails:
.get('/*', () => index)
This approach has a critical limitation: it only handles single-level routes. Any routes defined via .use() or .mount() (like /api/auth/* from better-auth) get overridden by the wildcard, causing 404s on legitimate API endpoints.
The wildcard pattern doesn't respect the plugin aggregation hierarchy when using .use().
Additional information
Current workaround (hacky solution):
.onError(({ code, request }) => {
if (code === "NOT_FOUND") {
const url = new URL(request.url);
const returnPath = url.pathname;
// Redirect to root with return path in query params
if (returnPath !== "/" && returnPath !== "/login") {
return redirect(`/?return=${encodeURIComponent(returnPath)}`);
}
return redirect("/");
}
})This forces all unknown routes to redirect to / with a query parameter, then client-side code reads the ?return= param and navigates to the intended path. This is unreliable and breaks direct navigation.
Expected solution:
Either:
- Fix onError to correctly return HTML bundles as HTML (not JSON) when the file type is .html
- Improve wildcard route matching to respect .use() and .mount() plugin routes (path priority: API endpoints → static files → wildcard fallback)
Have you try removing the node_modules and bun.lockb and try again yet?
YES