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