Build on top

SSE bridge

Expose the in-process evlog stream over Server-Sent Events for local devtools, dashboards, and any browser tab. Local development and self-hosted only.

The Nuxt module ships an opt-in Server-Sent Events endpoint that exposes the in-process default stream over HTTP. Anything that speaks SSE — a browser tab, a Tauri app, a CLI using fetch, a curl one-liner — can subscribe to live wide events.

Local development and long-lived self-hosted servers only.The bridge is built on the in-process stream. On serverless platforms (Vercel Functions, Cloudflare Workers, AWS Lambda…), each request is a separate isolate — the SSE consumer in one isolate will not see events emitted by other isolates, so the stream looks empty in production. Use a real broker (Redis Streams, NATS, Pub/Sub…) when you need cross-instance fan-out in serverless.It works perfectly in pnpm dev, on a Node / Bun / Deno container, on a long-lived VM, or on Fly / Railway-style instances.

Enable

In a Nuxt project the bridge is auto-enabled in dev — start pnpm dev and the stream URL is printed at startup:

  [evlog] Stream available at http://localhost:3000/api/_evlog/stream

No config needed. To override the default, pick the shape that matches your intent:

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['evlog/nuxt'],
  evlog: {
    transport: {
      // Auto-on in dev, off in production builds (current default — no config needed).
      // stream: undefined,

      // Boolean shorthand — force-enable in BOTH dev and prod.
      stream: true,

      // Explicit off — overrides the dev-mode auto-enable.
      // stream: false,

      // Full configuration.
      // stream: {
      //   enabled: true,
      //   endpoint: '/api/_evlog/stream',
      //   token: process.env.EVLOG_STREAM_TOKEN,
      //   heartbeatMs: 15_000,
      //   buffer: 500,
      // },
    },
  },
})

The four accepted shapes:

ValueDevProduction
undefined (default)enableddisabled
trueenabledenabled
falsedisableddisabled
{ enabled: true, ... }enabled (with config)enabled (with config)

The Nitro plugin auto-hooks the default in-process stream into evlog:drain, so every wide event also flows into any subscriber connected to the SSE route.

Try it in the playground

apps/playground ships a live demo. Run:

pnpm dev
# open http://localhost:3000/stream

You get a live event table with filters, a JSON inspector for each event, and buttons to fire demo requests. Useful to read the page source (apps/playground/app/pages/stream.vue) — it's ~25 lines of EventSource glue.

Endpoint

GET /api/_evlog/stream

HeaderNote
Authorization: Bearer <token>Required when token is configured
Accept: text/event-streamSet automatically by the browser EventSource

Query parameters:

  • ?since=<iso> — replay buffered events with event.timestamp >= since before switching to live mode

Response:

  • Content-Type: text/event-stream; charset=utf-8
  • X-Evlog-Version: <package version>

Wire format

Every SSE data: line is a versioned JSON envelope:

data: {"evlog":"1","type":"hello","data":{"evlogVersion":"2.16.0","bufferSize":500,"heartbeatMs":15000}}

data: {"evlog":"1","type":"replay","data":{...wide event...}}

data: {"evlog":"1","type":"event","data":{...wide event...}}

event: ping
data: {"evlog":"1","type":"ping","data":{"t":1730000000000}}
TypeWhen
helloFirst frame — server version + stream config
replayEach buffered event flushed when the client passed ?since=
eventEach new event drained after the connection opened
pingHeartbeat every heartbeatMs (default 15s), sent with event: ping

The evlog: "1" discriminant is the protocol version; future incompatible changes will bump it.

Security

ModeBehavior
token setAuthorization: Bearer <token> is required
token unset, host is localhostConnection allowed (dev convenience)
token unset, host is non-localhostOrigin must match Host (mirrors the ingest endpoint policy)

The endpoint always returns 404 when the stream is disabled (production builds without an explicit override) so it's a no-op in environments where you don't want it.

Minimal browser consumer

const es = new EventSource('/api/_evlog/stream')

es.onmessage = (e) => {
  const envelope = JSON.parse(e.data)
  if (envelope.evlog !== '1') return
  if (envelope.type === 'event' || envelope.type === 'replay') {
    handle(envelope.data)
  }
}

es.addEventListener('ping', () => {
  // heartbeat — connection alive
})

That's it — no SDK needed, the browser ships EventSource. For Node / Bun consumers, use fetch with streaming response and parse the same envelope.

Going further

  • Recipes — concrete patterns: build your own devtool, curl + jq inspection, replay-then-live, consumer-side analytics. See Recipes.
  • Playground demoapps/playground/app/pages/stream.vue is a ~30-line EventSource consumer with a live table, JSON inspector, and code samples per runtime. Run pnpm dev and visit /stream.