> ## Documentation Index
> Fetch the complete documentation index at: https://bunny.net/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Server-Sent Events

> Stream Server-Sent Events from an Edge Script, and keep the isolate alive while the connection is open.

An Edge Script can answer a request with a stream of [Server-Sent Events](/docs/cdn/server-sent-events). The script returns a `Response` that holds a `ReadableStream`, and the edge delivers each event as the script writes it.

The [CDN page](/docs/cdn/server-sent-events) describes how the network treats an event stream, including the heartbeat and the caching rules. This page describes what a script must do.

## Quickstart

This example sends one event immediately, and one more every 15 seconds. The interval also acts as the heartbeat that keeps the connection open.

```typescript theme={null}
import * as BunnySDK from "@bunny.net/edgescript-sdk";

const HEARTBEAT_MS = 15_000;

BunnySDK.net.http.serve(async (request: Request) => {
  const encoder = new TextEncoder();
  let timer: number;
  let onClosed: () => void;

  // Resolves when the client goes away and the runtime cancels the stream.
  const closed = new Promise<void>((resolve) => {
    onClosed = resolve;
  });

  const stream = new ReadableStream({
    start(controller) {
      let id = 0;

      const send = () => {
        id += 1;
        controller.enqueue(
          encoder.encode(
            `id: ${id}\nevent: tick\ndata: ${new Date().toISOString()}\n\n`,
          ),
        );
      };

      // Send the first event at once. The client waits until this byte.
      send();

      timer = setInterval(send, HEARTBEAT_MS);
    },
    cancel() {
      clearInterval(timer);
      onClosed();
    },
  });

  // It is important to call waitUntil; otherwise the script may be evicted
  // while the stream is still open.
  Bunny.v1.waitUntil(closed);

  return new Response(stream, {
    headers: {
      "content-type": "text/event-stream",
      "cache-control": "no-cache",
    },
  });
});
```

## Return the response before you wait

Return the `Response` at once, and let the stream fill in afterwards. A script that waits before it returns is treated as a slow origin: the edge allows 60 seconds for a response to start, and answers with `504 Gateway Timeout` when nothing arrives. This applies to any `await` before the `return`, and a subrequest is the common case.

```typescript theme={null}
// Wrong. If the subrequest takes longer than 60 seconds, the client gets a 504
// and never sees the stream.
const upstream = await fetch(slowOrigin);
return new Response(upstream.body, { headers });
```

```typescript theme={null}
// Right. The response goes back at once, and the subrequest runs inside the
// stream, so a slow origin delays an event instead of failing the request.
const stream = new ReadableStream({
  async start(controller) {
    const upstream = await fetch(slowOrigin);
    // ... write events from the upstream body
  },
});
return new Response(stream, { headers });
```

> [!WARNING]
>
> Write the first event as soon as the stream opens, and do not wait for the
> first real update. The client receives no part of the response until your
> script writes the first byte, and this includes the response headers.

## Keep the isolate alive

The isolate that answers a request can be evicted once the handler returns. An event stream lives longer than the handler, so pass a promise to [`waitUntil`](/docs/scripting/runtime#waituntil) that resolves only when the stream ends. Without it, the script can stop in the middle of a stream.

Always resolve that promise when the stream closes. A promise that never settles holds the isolate open after the client has gone.

## Caching

A stream that a script returns is not cached, and it needs no `Cache-Control` header. The edge serves every request to a standalone script from the script itself.

Set `Cache-Control: no-cache` anyway if the same code also runs behind a pull zone as [middleware](/docs/scripting/middleware/overview), where the normal [caching rules](/docs/cdn/server-sent-events#keep-the-stream-out-of-the-cache) apply.

## Limits

The [Edge Scripting limits](/docs/scripting/limits) apply, with one point to note: the 30-second CPU time limit measures processor time, and not the time a connection stays open. A stream that waits between events uses almost no CPU time. [Pricing](/docs/scripting/pricing) follows the same rule.

Keep the work inside the stream small. A script that builds each event with a subrequest reaches the subrequest limit quickly.

## References

* [Server-Sent Events on the CDN](/docs/cdn/server-sent-events)
* [WebSockets](./websockets), for two-way communication
* [Runtime - waitUntil](./runtime#waituntil)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.