Repository navigation
[Bug?]: v2 seroval-stream server function responses ship without Content-Type β proxies content-sniff text/plainΒ #2295
Description
Duplicates
- I have searched the existing issues
Latest version
- I have tested the latest version (2.0.0 source)
Current behavior π―
(Rewritten for SolidStart v2 as requested β the original v1 report is in the edit history.)
In v2, seroval-stream server-function responses are sent without a Content-Type header. packages/start/src/fns/handler.ts sets one only in the js mode branch:
h3Event.res.headers.set(BODY_FORMAT_KEY, BodyFormat.Seroval);
if (import.meta.env.SEROVAL_MODE === "js") {
h3Event.res.headers.set("content-type", "text/javascript");
return serializeToJSStream(instance, result);
}
return serializeToJSONStream(result); // <-- no content-type (also in the catch branch)Reverse proxies built on Go's net/http (kamal-proxy, Traefik-style middlewares, any httputil.ReverseProxy where a handler writes the response) content-sniff bodies that lack a Content-Type and inject one. The seroval chunk stream ;0x000048f1;{...} sniffs as text/plain; charset=utf-8, so clients and intermediaries downstream see a mislabeled response.
SolidStart's own v2 client is not broken by this β extractBody dispatches on X-Start-Type first, which is the right design. π But the missing header still has real costs:
- Anything that is not the Start client sees
text/plainβ logging/observability tooling, caches and CDNs applying content-type rules (e.g. compression or transformation filters keyed ontext/*), and browser devtools previews. - The response is at the mercy of whatever an intermediary guesses. A different sniffer could guess differently per body prefix, so behavior becomes deployment-dependent.
- HTTP semantics (RFC 9110 Β§8.3): a sender that knows the content's nature ought to declare it; omitting it is what invites sniffing in the first place.
- Historical evidence of the risk: in v1 (1.3.2) the same missing header + a Go proxy caused every client-side navigation to hang silently, because that client dispatched on Content-Type before
x-serialized. v2's dispatch order fixes the hang, but the root omission carried over.
Expected behavior π€
The server always sets an explicit Content-Type on seroval streams, in both the success and error paths β mirroring what js mode already does with text/javascript. application/octet-stream (or a dedicated type like application/x-seroval-stream) works; anything explicit prevents sniffing. One-line change next to the existing BODY_FORMAT_KEY header set.
Steps to reproduce πΉ
Steps:
- Any SolidStart 2.0.0 app with a
"use server"function called from the client. - Deploy behind a minimal Go reverse proxy (
httputil.ReverseProxywith any body-touching handler, or kamal-proxy as-is). - Call the server function; inspect the response headers in devtools.
- Observe injected
content-type: text/plain; charset=utf-8on the seroval stream (direct hit to the origin shows no Content-Type at all).
Context π¦
We run SolidStart behind kamal-proxy (Kamal is Rails' default deploy tooling, so this proxy is common). On v1 this exact omission produced a hard-to-debug production incident (silent navigation hang, no console errors); we work around it today with middleware that sets Content-Type: application/octet-stream on /_server responses that lack one. Declaring the type at the source would remove the workaround and make the transport deployment-independent. Happy to send a PR if the approach is acceptable.
Your environment π
System:
OS: Linux (Docker) / macOS 15 local
Binaries:
Node: 24.x
pnpm: 10.x
npmPackages:
@solidjs/start: 2.0.0 (source-reviewed; production repro observed on 1.3.2 behind the same proxy)
solid-js: 1.9.x
Proxy: kamal-proxy (Go net/http), TLS terminated at proxy
Activity
birkskyum commentedon Aug 17, 2026
@NicolasdRa ,, SolidStart v1 / vinxi is obsoleted, can you convert this issue to be entirely about SolidStart v2?
[-][Bug?]: json serialization mode sends server-function responses without Content-Type β proxies sniff text/plain and client-side navigation hangs silently[/-][+][Bug?]: v2 seroval-stream server function responses ship without Content-Type β proxies content-sniff text/plain[/+]NicolasdRa commentedon Aug 18, 2026
@birkskyum Done β rewrote the issue to be entirely about v2 (
packages/start/src/fns/handler.ts). The v1 report is in the edit history. Short version: v2'sX-Start-Typedispatch already protects Start's own client, but the seroval stream still ships without a Content-Type, so Go-based proxies snifftext/plainonto it. Proposal is a one-liner: set an explicit type next to the existingBODY_FORMAT_KEYset (both success and error paths), mirroring js mode'stext/javascript. Happy to send a PR if that approach works for you.birkskyum commentedon Aug 18, 2026
Confirmed. The v2 JSON-mode handler leaves
Content-Typeunset in both the success and error serialization paths. This is the v2 counterpart of the issue already fixed for 1.x in #2092.Letβs use
text/plain; charset=utf-8, since the Seroval framing is textual and this also avoids adapters treating it as binary/base64. A PR adding the header to both paths, regression tests, and a patch changeset would be welcome.birkskyum commentedon Aug 18, 2026
@NicolasdRa a PR would be very welcome.