Streaming
Node and Bun Functions stream response bodies to the client as they are produced, with no flag to set.
A Node or Bun Function streams by default. Whatever your code writes to the response goes out as it is written, so server-sent events, React Server Component payloads and long JSON exports reach the browser progressively instead of landing in one block at the end.
What it looks like
// api/events.js
import http from 'node:http';
const server = http.createServer((req, res) => {
res.writeHead(200, {
'content-type': 'text/event-stream',
'cache-control': 'no-store',
});
let tick = 0;
const timer = setInterval(() => {
tick += 1;
res.write(`data: ${JSON.stringify({ tick, at: new Date().toISOString() })}\n\n`);
if (tick === 10) {
clearInterval(timer);
res.end();
}
}, 1000);
req.on('close', () => clearInterval(timer));
});
server.listen(3000);version: 4
functions:
api/events.js:
runtime: node-22
max_duration: 60
routes:
- source: ^/events$
destination: /api/events.jsEach res.write is flushed on its own rather than buffered until the handler returns, so the client sees the first event about a second after connecting. Gigadrive Network captures the server your code creates and assigns it a port, so the number you pass to listen is ignored.
Response headers
The response body passes through untouched. Caching and compression headers are handled at the edge:
- A streamed response you want cached needs an explicit
cache-control. A Function that sets none getscache-control: no-store. - Compression is negotiated at the edge, not in your Function. Your code never sees the client's
accept-encoding, and the CDN compresses the response and sets its owncontent-encodingandcontent-length. Write plain bytes.
Where a stream ends
A stream that overruns is truncated, not failed
The invocation deadline covers the response body as well as the headers. If a stream is still running when max
duration expires, the status line has already been sent and cannot be changed, so
the body stream is errored instead. The client sees a 200 that stops early. The same is true of the 32 MiB response
ceiling: the connection closes without its terminating chunk, which is how a client detects the truncation.
Set max_duration to something above the longest stream you expect, and close the stream from your own code when a client disconnects. In the example, req.on('close', ...) stops the interval when a browser navigates away; without it the Function keeps producing events nobody reads until the deadline fires.
A WebSocket is the better fit when the client needs to send as well as receive.
