-
-
Notifications
You must be signed in to change notification settings - Fork 806
server.ts fetch handler: 404 responses silently replaced with SPA HTML in dev mode #4162
Description
Problem
In Vite dev mode, when server.ts fetch handler returns a Response with status 404, Nitro's dispatchFetch pipeline silently replaces it with the SPA index.html (status 200, content-type text/html).
This breaks any API that legitimately returns 404 (e.g., "entity not found").
Reproduction
// server.ts
export default {
fetch: (request: Request) => {
const url = new URL(request.url)
if (url.pathname === '/api/users/999') {
return new Response(
JSON.stringify({ code: 'NOT_FOUND', status: 404, message: 'User not found' }),
{ status: 404, headers: { 'content-type': 'application/json' } }
)
}
return new Response('ok')
}
}curl http://localhost:3000/api/users/999
# Expected: {"code":"NOT_FOUND","status":404,"message":"User not found"}
# Actual: <!doctype html><html>... (SPA index.html)Debug findings
Using logs in node_modules/nitro/dist/vite.mjs:
[nitro:dev] devApp.fetch → status=404 (correct — no file-based route)
[nitro:dev] envRes (server.ts) → status=404 type=application/json (correct — our handler)
[nitro:dev] but client receives → status=200 type=text/html (WRONG — overridden!)
The server.ts fetch handler correctly returns a JSON 404 Response, but somewhere between dispatchFetch return and sendNodeResponse, the response is replaced with SPA HTML.
Expected behavior
server.ts fetch handler should have full control over the Response. If it returns a 404 with application/json content-type, that should be sent to the client as-is.
Only "unmatched" requests (where server.ts returns undefined or doesn't handle the path) should trigger SPA fallback.
Environment
- nitro: 3.0.260311-beta
- Vite 8 dev mode