-
Notifications
You must be signed in to change notification settings - Fork 77
Description
Bug Description:
Summary
@hono/node-server v1.18.0 introduced a regression that breaks real-time Server-Sent Events (SSE) streaming. The new pre-read logic delays the first chunk delivery, causing browsers to show "no response" and breaking the real-time nature of SSE connections.
Affected Versions
- Working: v1.17.1 and earlier
- Broken: v1.18.0 and later
Root Cause
The v1.18.0 release introduced a change described as "feat: always respond res.body" which added pre-read logic that attempts to read the first 2 chunks of a ReadableStream before sending headers and data to the client.
Problematic code in src/listener.ts (lines ~138-175):
if (res.body) {
const reader = res.body.getReader()
const values: Uint8Array[] = []
// This pre-read logic breaks real-time SSE streaming
for (let i = 0; i < 2; i++) {
currentReadPromise = reader.read()
const chunk = await readWithoutBlocking(currentReadPromise)
// ... waits to read chunks before sending anything
}
// Headers/data only sent after pre-reading
outgoing.writeHead(res.status, resHeaderRecord)
}Impact
- Browser DevTools shows "no response": SSE connections appear stuck/pending
- Delayed first events: Users don't see SSE data until multiple chunks are buffered
- Breaks real-time applications: Chat apps, live updates, progress indicators, etc.
- Production issue: Affects any application using SSE with async data generation
Reproduction
// SSE endpoint that generates data asynchronously
app.get('/events', (c) => {
const stream = new ReadableStream({
async start(controller) {
controller.enqueue('data: first event\n\n')
// Any async operation (DB query, API call, etc.)
await new Promise(resolve => setTimeout(resolve, 100))
controller.enqueue('data: second event\n\n')
controller.close()
}
})
c.header('Content-Type', 'text/event-stream')
return c.body(stream)
})Expected: First event appears immediately in browser
Actual (v1.18.0): Browser shows "no response" until second event is ready
Suggested Fix
Add a fast-path for SSE responses that bypasses the pre-read logic:
// Fast-path for Server-Sent Events
const contentTypeHeader = resHeaderRecord["content-type"] as string | undefined
if (contentTypeHeader && /^text\/event-stream\b/i.test(contentTypeHeader) && res.body) {
// Stream immediately without pre-reading
outgoing.writeHead(res.status, resHeaderRecord)
flushHeaders(outgoing)
await writeFromReadableStream(res.body, outgoing)
return
}Environment
- Node.js: Any version
- Browser: All browsers (Chrome DevTools most visible)
- Use case: Any SSE implementation with async data generation
This regression significantly impacts real-time web applications and should be prioritized for the next patch release.