Which project does this relate to?
Start
Describe the bug
Server route handlers (createFileRoute with server.handlers.GET) return the correct content-type on GET, but HEAD to the same path returns content-type: text/html; charset=utf-8 (the SSR default from getStartResponseHeaders) and an empty body. Handler appears not to run for HEAD; the SSR shell takes over.
Real consumers (browsers, RSS readers, sitemap parsers) use GET so impact is small, but it breaks tools that do HEAD probes (curl -sI, link checkers, uptime monitors) and conflicts with the HTTP spec — RFC 9110 §9.3.2 requires HEAD to send the same header fields as GET.
Confirmed still reproducing on @tanstack/react-start 1.167.49 + @tanstack/react-router 1.168.24 (latest as of filing).
Your Example Website or App
https://fasu.dev/sitemap.xml (TanStack Start on Cloudflare Workers)
Route source:
// src/routes/sitemap[.]xml.ts
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/sitemap.xml")({
server: {
handlers: {
GET: () =>
new Response(buildSitemap(), {
headers: {
"content-type": "application/xml; charset=utf-8",
"cache-control": "public, max-age=3600",
},
}),
},
},
});
Steps to Reproduce the Bug or Issue
- Create a server-only file route with a
GET handler returning new Response(body, { headers: { "content-type": "application/xml; charset=utf-8" } }).
curl -i https://fasu.dev/sitemap.xml → content-type: application/xml; charset=utf-8 ✅
curl -I https://fasu.dev/sitemap.xml → content-type: text/html; charset=utf-8 ❌
Reproduces in both wrangler dev and vite preview. Same behavior across /sitemap.xml, /llms.txt, /rss.xml, and a /windsurf shell-script proxy route.
Expected behavior
HEAD returns the same headers as GET. Per RFC 9110 §9.3.2: "the server SHOULD send the same header fields in response to a HEAD request as it would have sent if the request method had been GET."
Screenshots or Videos
$ curl -sI https://fasu.dev/sitemap.xml
HTTP/2 200
content-type: text/html; charset=utf-8 # ❌
...
$ curl -s -o /dev/null -D - https://fasu.dev/sitemap.xml
HTTP/2 200
content-type: application/xml; charset=utf-8 # ✅
cache-control: public, max-age=3600
...
Platform
- Router / Start Version:
@tanstack/react-start 1.167.49, @tanstack/react-router 1.168.24, @tanstack/router-plugin 1.167.27
- OS: Linux (Cloudflare Workers runtime + local repro)
- Browser: n/a (curl)
- Bundler: vite 8.0.10 +
@cloudflare/vite-plugin 1.33.2
- Wrangler: 4.85.0
Additional context
Tracing through the bundle: handleServerRoutes builds routeMiddlewares = [handlerMiddleware, executeRouter]. On GET, handler middleware returns a Response and executeMiddleware short-circuits. On HEAD, the handler appears to be skipped (or its return discarded) and executeRouter runs, invoking defaultStreamHandler whose responseHeaders default to text/html; charset=utf-8.
Likely culprit in handleServerRoutes:
const handler = handlers[request.method.toUpperCase()] ?? handlers["ANY"];
HEAD has no entry, no ANY fallback, so the handler is never registered and the chain falls through to SSR.
Possible fixes:
- For server-only routes (no
component), auto-fall-back HEAD → GET handler with body stripped (matches RFC 9110 and common framework behavior).
- Or document that users must explicitly add a
HEAD handler when defining GET.
Which project does this relate to?
Start
Describe the bug
Server route handlers (
createFileRoutewithserver.handlers.GET) return the correctcontent-typeonGET, butHEADto the same path returnscontent-type: text/html; charset=utf-8(the SSR default fromgetStartResponseHeaders) and an empty body. Handler appears not to run for HEAD; the SSR shell takes over.Real consumers (browsers, RSS readers, sitemap parsers) use GET so impact is small, but it breaks tools that do HEAD probes (
curl -sI, link checkers, uptime monitors) and conflicts with the HTTP spec — RFC 9110 §9.3.2 requires HEAD to send the same header fields as GET.Confirmed still reproducing on
@tanstack/react-start1.167.49 +@tanstack/react-router1.168.24 (latest as of filing).Your Example Website or App
https://fasu.dev/sitemap.xml (TanStack Start on Cloudflare Workers)
Route source:
Steps to Reproduce the Bug or Issue
GEThandler returningnew Response(body, { headers: { "content-type": "application/xml; charset=utf-8" } }).curl -i https://fasu.dev/sitemap.xml→content-type: application/xml; charset=utf-8✅curl -I https://fasu.dev/sitemap.xml→content-type: text/html; charset=utf-8❌Reproduces in both
wrangler devandvite preview. Same behavior across/sitemap.xml,/llms.txt,/rss.xml, and a/windsurfshell-script proxy route.Expected behavior
HEAD returns the same headers as GET. Per RFC 9110 §9.3.2: "the server SHOULD send the same header fields in response to a HEAD request as it would have sent if the request method had been GET."
Screenshots or Videos
Platform
@tanstack/react-start1.167.49,@tanstack/react-router1.168.24,@tanstack/router-plugin1.167.27@cloudflare/vite-plugin1.33.2Additional context
Tracing through the bundle:
handleServerRoutesbuildsrouteMiddlewares = [handlerMiddleware, executeRouter]. On GET, handler middleware returns a Response andexecuteMiddlewareshort-circuits. On HEAD, the handler appears to be skipped (or its return discarded) andexecuteRouterruns, invokingdefaultStreamHandlerwhoseresponseHeadersdefault totext/html; charset=utf-8.Likely culprit in
handleServerRoutes:HEAD has no entry, no
ANYfallback, so the handler is never registered and the chain falls through to SSR.Possible fixes:
component), auto-fall-back HEAD → GET handler with body stripped (matches RFC 9110 and common framework behavior).HEADhandler when definingGET.