Describe the bug
vite preview unconditionally registers @polka/compression ahead of every response (packages/vite/src/node/preview.ts:239 — app.use(compression())). When a request has Accept-Encoding: gzip, polka wraps res.writeHead/write/end/on and defers writeHead to its start() function — even when the response is text/event-stream and won't actually be compressed (chunked SSE has no Content-Length, fails polka's size >= threshold check).
Under HTTP/2 (which vite preview enables when preview.https is set), this wrapping corrupts the HTTP/2 stream lifecycle when the SSE stream is aborted client-side. Other fetch calls multiplexed on the same connection get stuck pending for 10–30 seconds before resolving, or never resolve. Page navigations away from any route that holds an open SSE stream stall visibly.
The compression itself is not the issue — it's correctly skipped for SSE due to the size threshold. The bug is in the proxying of res methods that happens before the compression decision.
Suggested fix
Skip polka's response wrapping for text/event-stream in vite preview. Possible shapes:
- bump the bundled
@polka/compression once lukeed/polka#221 or equivalent lands
- guard
app.use(compression()) in packages/vite/src/node/preview.ts to skip SSE paths
- expose a
preview.compression option so consumers can opt out
Reproduction
https://github.com/rsergeenko/vite-preview-sse-http2-repro
Steps to reproduce
npm install && npm run build && npm run preview
- Open
https://localhost:4173/ (accept the self-signed cert)
- Click Open SSE — opens a long-lived
text/event-stream connection via fetch
- Wait ~2 seconds (a couple of events stream)
- Click Fetch /data — issues a concurrent
GET /data (small JSON response)
- DevTools → Network: observe
/data sits pending for 10–30 seconds before resolving
Expected: /data resolves in single-digit ms.
System Info
- Vite 8.0.16 (also reproduces on 7.x)
- Node 22.x
- macOS 25.5.0, Chromium 148
- @polka/compression 1.0.0-next.25 (bundled with Vite)
Used Package Manager
yarn
Logs
No response
Validations
Describe the bug
vite previewunconditionally registers@polka/compressionahead of every response (packages/vite/src/node/preview.ts:239 — app.use(compression())). When a request hasAccept-Encoding: gzip, polka wrapsres.writeHead/write/end/onand deferswriteHeadto itsstart()function — even when the response istext/event-streamand won't actually be compressed (chunked SSE has noContent-Length, fails polka'ssize >= thresholdcheck).Under HTTP/2 (which
vite previewenables whenpreview.httpsis set), this wrapping corrupts the HTTP/2 stream lifecycle when the SSE stream is aborted client-side. Otherfetchcalls multiplexed on the same connection get stuckpendingfor 10–30 seconds before resolving, or never resolve. Page navigations away from any route that holds an open SSE stream stall visibly.The compression itself is not the issue — it's correctly skipped for SSE due to the size threshold. The bug is in the proxying of
resmethods that happens before the compression decision.Suggested fix
Skip polka's response wrapping for
text/event-streaminvite preview. Possible shapes:@polka/compressiononce lukeed/polka#221 or equivalent landsapp.use(compression())inpackages/vite/src/node/preview.tsto skip SSE pathspreview.compressionoption so consumers can opt outReproduction
https://github.com/rsergeenko/vite-preview-sse-http2-repro
Steps to reproduce
npm install && npm run build && npm run previewhttps://localhost:4173/(accept the self-signed cert)text/event-streamconnection viafetchGET /data(small JSON response)/datasitspendingfor 10–30 seconds before resolvingExpected:
/dataresolves in single-digit ms.System Info
Used Package Manager
yarn
Logs
No response
Validations