vite preview over HTTP/2: @polka/compression middleware appears to break text/event-stream (SSE) — concurrent fetches stall
#22653
Replies: 3 comments
|
Hi! This is definitely a bug and you should file it (or put up a PR!). The issue of To fix this temporarily in Vite custom configuration, you can use a plugin with // vite.config.js
export default {
plugins: [{
name: 'bypass-sse-compression',
configurePreviewServer(server) {
server.middlewares.use((req, res, next) => {
if (req.headers['accept'] === 'text/event-stream' || req.url.includes('/sse-endpoint')) {
delete req.headers['accept-encoding'];
}
next();
});
}
}]
}Putting up a PR in Vite's // Skip compression for SSE
if (req.headers['accept'] === 'text/event-stream') {
return next();
} |
|
This does look like a genuine bug in the way At This isn't unique to Once compression kicks in, A workaround that avoids touching Vite itself: function noCompressSse() {
return {
name: 'no-compress-sse',
configurePreviewServer(server) {
server.middlewares.use((req, res, next) => {
if (req.url?.startsWith('/api/stream')) {
delete req.headers['accept-encoding']
}
next()
})
},
}
}The real fix belongs upstream: |
|
Your diagnosis holds up, and it's worth separating it from the one above, because the two point at different mechanisms and only one of them survives contact with the source. I read Compression does not engage for ordinary SSE. So the gzip-buffering explanation doesn't apply to the typical case. What actually breaks is the wrapping, exactly as you said. Two things, both independent of whether compression runs:
Measured, HTTP/1.1, handler calls Headers are held for the full 400 ms and nothing is compressed. The 2 KB row is the corner where the threshold is cleared — there Second probe — client aborts before the server writes anything, That's the one I'd point at for your symptom. The cleanup handler an SSE route registers on Both probes are ~40 lines of On the three directions you listed: stripping For an upstream fix, gating on the request's Worth filing, and worth splitting: the deferral is a |
Uh oh!
There was an error while loading. Please reload this page.
Description
Posting this as a discussion since I think it's a bug but the previous report on the same code path (#15987) was a discussion too, and I'd like to confirm before filing.
What we see
Under
vite previewwith HTTPS (so HTTP/2 is in play), opening a long-livedtext/event-streamresponse viafetchand then making concurrentfetchcalls on the same connection causes the concurrent calls to hangpendingin DevTools for 10–30s. Page navigations away from a route that holds an open SSE stream visibly stall — the destination route's__data.json(or any other concurrent fetch) is the one that gets stuck.The mechanism:
vite previewunconditionally registers@polka/compressionahead of every response (packages/vite/src/node/preview.ts:239). When the 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). The wrapping itself, not the compression, is what corrupts the HTTP/2 stream lifecycle when the SSE stream is aborted client-side.Isolation
Stripping
Accept-Encodingfor the SSE URL via aconfigurePreviewServerplugin (which makes polka take itsif (!encoding) return next();early-return and skip wrappingres) fully resolves the symptom. That isolates the bug to polka's response-wrapping under HTTP/2, not to compression of SSE itself.Where we hit it
SvelteKit 2.63.1 switched
query.liveto SSE (sveltejs/kit#15957). Every navigation away from a route with an open live query stalls undervite preview— our e2e tests time out, manual preview sessions feel broken. Production deploys (adapter-node) are unaffected. Not specific to SvelteKit; reproduces with any SSE consumer usingfetchover HTTP/2 preview.Related
ERR_HTTP2_INVALID_STREAM), still openQuestion
Is this a known limitation, or should we file a bug? If a fix is desired, the obvious shapes would be:
@polka/compressiononce lukeed/polka#221 or equivalent landsapp.use(compression())inpackages/vite/src/node/preview.tsto skip SSE pathspreview.compressionoption so consumers can opt outHappy to put up a PR if a direction is preferred.
System info
Vite 8.0.16 / Node 22 / Chromium 148. Reproduced on Vite 7.x as well.
All reactions