Realtime includes

An include can subscribe to a realtime channel. On first load it renders server-side like any other include; after that, a client-side SSE subscription re-fetches the fragment whenever the server publishes to the channel.

<bext-include src="/partials/order-status"
  realtime="orders:123"
></bext-include>

No framework, no state management. Just a <div> that updates itself when the server says it should.

Attributes

Attribute Purpose
realtime Channel/topic name to subscribe to
realtime-mode replace (default), append, prepend
realtime-event Filter to a specific event type
realtime-transport sse, ws, or auto

How it works

On SSR: The include resolves normally — full server-side HTML. bext wraps the result in a container with realtime metadata:

<div data-bext-realtime="orders:123"
     data-bext-include-src="/partials/order-status"
     data-bext-realtime-mode="replace">
  <p>Order #123: Preparing</p>
</div>

On the client: bext injects a tiny inline script (once per page) that discovers all [data-bext-realtime] elements and subscribes to their channels via EventSource:

// Injected automatically when any realtime include is present
var es = new EventSource('/__bext/realtime/sse?topic=orders:123');
es.onmessage = function(ev) {
  // Re-fetch the include HTML
  fetch('/__bext/include?src=/partials/order-status')
    .then(r => r.text())
    .then(html => {
      el.innerHTML = html;  // or append/prepend based on mode
    });
};

On publish: Any server-side code that updates the underlying data publishes to the channel:

realtime_hub.publish("orders:123", json!({
    "event": "order-update",
    "status": "shipped"
}));

All subscribed clients re-fetch the include automatically.

Update modes

Control how updates apply to the DOM:

<!-- Replace: new content replaces current (default) -->
<bext-include src="/partials/status"
  realtime="orders:123"
  realtime-mode="replace"
></bext-include>

<!-- Append: new content added to the end (live feeds) -->
<bext-include src="/partials/latest-message"
  realtime="chat:42"
  realtime-mode="append"
></bext-include>

<!-- Prepend: new content added to the start (notifications) -->
<bext-include src="/partials/notification"
  realtime="user:42:notifs"
  realtime-mode="prepend"
></bext-include>

Event filtering

A channel can carry multiple event types. Use realtime-event to subscribe to just one:

<!-- Only re-render when the server publishes event="price-change" -->
<bext-include src="/partials/price-tag"
  realtime="stock:AAPL"
  realtime-event="price-change"
></bext-include>

<!-- Only re-render when event="volume-update" -->
<bext-include src="/partials/volume-meter"
  realtime="stock:AAPL"
  realtime-event="volume-update"
></bext-include>

Both subscribe to stock:AAPL but react to different events. The server publishes:

realtime_hub.publish("stock:AAPL", json!({
    "event": "price-change",
    "price": 171.25
}));

Only the price-tag include re-fetches. The volume-meter ignores this event.

Channel authorization

The realtime hub enforces per-channel auth rules (same system as other realtime subscriptions):

[realtime.auth]
# User-specific channels require authentication
"user:*" = { auth = "required" }

# Admin-only channels
"admin:*" = { auth = "required", role = "admin" }

# Public channels — no auth needed
"public:*" = { auth = "none" }

Unauthorized subscribers receive 403 Forbidden from the SSE endpoint. The include's SSR still happens (subject to the include's own auth attribute) — only the live subscription is gated.

Pairing with caching

A realtime include's initial SSR can still be cached with a short TTL to absorb request bursts:

<bext-include src="/partials/live-scores"
  realtime="scores:match-42"
  ttl="5s"
></bext-include>

- The first request resolves and caches for 5 seconds.

- Subsequent initial page loads (during the 5s window) hit cache.

- The client-side subscription keeps the content fresh after load.

Don't use long TTLs — the cache is a safety net, not the source of truth. The realtime channel is what keeps the content fresh.

Connection management

Multiple includes on the same page sharing the same channel share a single connection:

<bext-include src="/partials/order-header" realtime="orders:123"></bext-include>
<bext-include src="/partials/order-timeline" realtime="orders:123"></bext-include>
<bext-include src="/partials/order-total" realtime="orders:123"></bext-include>

One EventSource is opened for orders:123; all three includes listen on it and re-fetch when an event arrives. The batch endpoint /__bext/include/batch resolves all three in a single request:

POST /__bext/include/batch
[
  { "src": "/partials/order-header" },
  { "src": "/partials/order-timeline" },
  { "src": "/partials/order-total" }
]

Returns an array of resolved HTML strings, each swapped into its respective element.

Transport selection

<bext-include src="/partials/data"
  realtime="channel"
  realtime-transport="auto"
></bext-include>
Value Transport Use
auto (default) SSE → WS fallback Best compatibility
sse Server-Sent Events Simple, works through most proxies
ws WebSocket Lower latency, bidirectional

Auto starts with SSE because it's simpler and passes through more proxies. Falls back to WebSocket only if SSE is unavailable (rare).

Client-side reconnection

The injected script handles automatic reconnection:

- On disconnect, it waits 1-5 seconds (exponential backoff) and reconnects.

- On the server side, bext's realtime hub resumes the subscription seamlessly.

- In-flight events that occurred during the disconnect are replayed if the client sends a Last-Event-ID header (standard SSE behavior).

Performance

- One EventSource per unique channel per page (not per include).

- Re-fetch uses /__bext/include?src=... which respects the include's cache.

- Server-side publishes fan out via the realtime hub's internal pub/sub.

- With Redis backend, publishes propagate across horizontally-scaled instances.

Example: live dashboard

<body>
<h1>Dashboard</h1>

<section>
  <h2>Active orders</h2>
  <bext-include src="/partials/active-order-count"
    realtime="system:orders"
    ttl="10s"
  ></bext-include>
</section>

<section>
  <h2>Recent events</h2>
  <bext-include src="/partials/event-feed"
    realtime="system:events"
    realtime-mode="prepend"
  >Loading events...</bext-include>
</section>

<section>
  <h2>Server health</h2>
  <bext-include src="/partials/health-metrics"
    realtime="system:health"
    realtime-event="alert"
    ttl="30s"
  ></bext-include>
</section>
</body>

Three different channels, three different update modes, all self-updating. No dashboard framework, no WebSocket management code.

See also

- Realtime Hub — the underlying SSE/WS infrastructure

- WebSockets & SSE — when to use each transport

- Caching includes — pairing cache with realtime