Htmx Websockets
Real-time updates with the HTMX WebSocket extension and server-sent events
You are an expert in implementing real-time, bidirectional communication in hypermedia-driven applications using the HTMX WebSocket extension and server-sent events (SSE).
## Key Points
- `ws-connect="/path"` — establishes a WebSocket connection
- `ws-send` — marks a form or element to send messages over the socket
- Messages from the server are HTML fragments that HTMX swaps into the DOM using standard targeting and swap rules
- `sse-connect="/path"` — opens an SSE connection
- `sse-swap="event-name"` — swaps content when the named event arrives
- Multiple elements can listen for different event types on the same connection
- **Send HTML fragments, not JSON.** Follow the HTMX philosophy. The server should render HTML and push it to clients. This eliminates client-side templating logic.
- **Use out-of-band swaps for multi-region updates.** A single WebSocket message can update multiple DOM targets using `hx-swap-oob`, keeping the protocol simple.
- **Implement server-side heartbeats.** Send a periodic comment line (`: heartbeat`) in SSE or a ping frame in WebSockets to keep the connection alive through proxies and load balancers.
- **Forgetting to load the extension.** The `ws` and `sse` extensions are not bundled with core HTMX. You must include the extension script and activate it with `hx-ext="ws"` or `hx-ext="sse"`.
## Quick Example
```json
{"message": "Hi Alice!", "HEADERS": {"HX-Request": "true"}}
```
```html
<div id="chat-messages" hx-swap-oob="beforeend">
<div class="message">
<strong>Bob:</strong> Hi Alice!
</div>
</div>
```skilldb get htmx-skills/Htmx WebsocketsFull skill: 239 linesWebSocket Extension — HTMX
You are an expert in implementing real-time, bidirectional communication in hypermedia-driven applications using the HTMX WebSocket extension and server-sent events (SSE).
Overview
HTMX provides two extensions for real-time server communication: the WebSocket extension (ws) for bidirectional messaging and the SSE extension (sse) for server-to-client streaming. Both follow the HTMX philosophy of returning HTML fragments from the server that are swapped directly into the DOM. This avoids the typical pattern of receiving JSON over a socket and manually building DOM elements in JavaScript.
Core Concepts
WebSocket Extension (ws)
The ws extension manages the WebSocket lifecycle (connect, reconnect, message handling) declaratively via HTML attributes:
ws-connect="/path"— establishes a WebSocket connectionws-send— marks a form or element to send messages over the socket- Messages from the server are HTML fragments that HTMX swaps into the DOM using standard targeting and swap rules
SSE Extension (sse)
The sse extension consumes server-sent events:
sse-connect="/path"— opens an SSE connectionsse-swap="event-name"— swaps content when the named event arrives- Multiple elements can listen for different event types on the same connection
Automatic Reconnection
Both extensions automatically reconnect on connection loss using an exponential backoff strategy, making them resilient to transient network issues.
Implementation Patterns
Real-Time Chat with WebSockets
<!-- Load the extension -->
<script src="https://unpkg.com/htmx-ext-ws@2.0.0/ws.js"></script>
<div hx-ext="ws" ws-connect="/chat/room/42">
<!-- Message display area -->
<div id="chat-messages">
<!-- Existing messages rendered server-side -->
<div class="message">
<strong>Alice:</strong> Hello everyone!
</div>
</div>
<!-- Send form -->
<form ws-send>
<input name="message" placeholder="Type a message..."
autocomplete="off" required>
<button type="submit">Send</button>
</form>
</div>
When the form is submitted, HTMX serializes it as JSON and sends it over the WebSocket:
{"message": "Hi Alice!", "HEADERS": {"HX-Request": "true"}}
The server processes the message and broadcasts an HTML fragment to all connected clients:
<div id="chat-messages" hx-swap-oob="beforeend">
<div class="message">
<strong>Bob:</strong> Hi Alice!
</div>
</div>
The hx-swap-oob="beforeend" tells HTMX to append the new message to #chat-messages on every connected client.
Live Notifications with SSE
<script src="https://unpkg.com/htmx-ext-sse@2.0.0/sse.js"></script>
<div hx-ext="sse" sse-connect="/notifications/stream">
<!-- Notification badge updates -->
<span id="notif-count"
sse-swap="notification-count"
hx-swap="innerHTML">
0
</span>
<!-- Toast notifications -->
<div id="toast-container"
sse-swap="new-notification"
hx-swap="afterbegin">
</div>
</div>
Server sends events:
event: notification-count
data: <span>3</span>
event: new-notification
data: <div class="toast" role="alert">You have a new message from Alice.</div>
Live Dashboard Updates
<div hx-ext="sse" sse-connect="/dashboard/stream">
<div class="metric-card">
<h3>Active Users</h3>
<div id="active-users" sse-swap="active-users" hx-swap="innerHTML">
Loading...
</div>
</div>
<div class="metric-card">
<h3>Orders Today</h3>
<div id="orders-today" sse-swap="orders-today" hx-swap="innerHTML">
Loading...
</div>
</div>
<div class="metric-card">
<h3>Revenue</h3>
<div id="revenue" sse-swap="revenue" hx-swap="innerHTML">
Loading...
</div>
</div>
</div>
Server sends periodic updates:
event: active-users
data: <span class="metric">1,247</span>
event: orders-today
data: <span class="metric">384</span>
event: revenue
data: <span class="metric">$42,850</span>
WebSocket with Out-of-Band Updates
The server can update multiple parts of the page in a single WebSocket message:
<div hx-ext="ws" ws-connect="/game/lobby">
<div id="player-list">
<!-- players listed here -->
</div>
<div id="game-status">Waiting for players...</div>
<div id="chat-log"></div>
</div>
Server broadcasts a single message containing multiple OOB fragments:
<div id="player-list" hx-swap-oob="innerHTML">
<div class="player">Alice (Ready)</div>
<div class="player">Bob (Not Ready)</div>
<div class="player">Charlie (Ready)</div>
</div>
<div id="game-status" hx-swap-oob="innerHTML">
3 players connected — waiting for all to be ready
</div>
<div id="chat-log" hx-swap-oob="beforeend">
<p><em>Charlie has joined the lobby.</em></p>
</div>
Connection Status Indicator
<div hx-ext="ws" ws-connect="/updates"
id="ws-container">
<span id="connection-status" class="connected">Connected</span>
<div id="content">...</div>
</div>
<script>
document.body.addEventListener("htmx:wsOpen", function(evt) {
document.getElementById("connection-status").textContent = "Connected";
document.getElementById("connection-status").className = "connected";
});
document.body.addEventListener("htmx:wsClose", function(evt) {
document.getElementById("connection-status").textContent = "Reconnecting...";
document.getElementById("connection-status").className = "disconnected";
});
</script>
Core Philosophy
Real-time communication in HTMX follows the same hypermedia principle as the rest of the library: the server sends HTML, and the client swaps it into the DOM. There are no JSON messages to parse, no client-side templates to maintain, and no state synchronization layer. A WebSocket message or SSE event contains rendered HTML fragments that HTMX inserts directly, keeping the rendering logic centralized on the server.
The choice between WebSockets and SSE is driven by the direction of communication, not by a preference for one technology over the other. SSE is simpler, works over standard HTTP, auto-reconnects natively, and is the right choice when the server pushes updates to the client. WebSockets add bidirectional messaging, which is necessary only when clients need to send data back (chat, collaborative editing, gaming). Choosing SSE when you only need server-to-client streaming avoids the operational complexity of managing WebSocket connections.
Both extensions embrace automatic reconnection with exponential backoff, which makes them resilient to transient network failures without any custom retry logic. However, reconnection creates a gap during which the client may miss updates. The architecture must account for this by either sending a full state snapshot on reconnect or implementing a catch-up mechanism. This is a server-side concern, not a client-side one, which aligns with HTMX's philosophy of keeping intelligence on the server.
Anti-Patterns
-
Sending JSON over WebSockets and building DOM manually in JavaScript. This defeats HTMX's purpose. If the server sends HTML fragments, HTMX handles all DOM insertion automatically. Sending JSON reintroduces client-side templating complexity.
-
Opening multiple SSE connections to the same domain. Browsers limit concurrent HTTP/1.1 connections per domain (typically six). Each SSE stream uses one. Too many streams can starve regular HTTP requests. Consolidate events into a single SSE endpoint or use HTTP/2.
-
Ignoring reconnection state. After a WebSocket or SSE reconnection, the client may have missed updates. Without a resynchronization mechanism, the UI silently becomes stale. Send a full state snapshot on connection or request one from the client.
-
Accumulating DOM content indefinitely in live feeds. Chat messages, log entries, and notification lists grow without bound. Without periodic trimming, the page accumulates thousands of DOM nodes, degrading rendering performance and consuming memory.
-
Forgetting to load the extension scripts. The
wsandsseextensions are not part of core HTMX. Omitting the extension script and addinghx-ext="ws"produces no error but also no functionality, leading to silent failures that are hard to debug.
Best Practices
- Use SSE for server-to-client streaming, WebSockets for bidirectional messaging. SSE is simpler, works over standard HTTP, and auto-reconnects natively. Only use WebSockets when clients need to send messages back.
- Send HTML fragments, not JSON. Follow the HTMX philosophy. The server should render HTML and push it to clients. This eliminates client-side templating logic.
- Use out-of-band swaps for multi-region updates. A single WebSocket message can update multiple DOM targets using
hx-swap-oob, keeping the protocol simple. - Namespace your SSE event types. Use descriptive event names like
user-joined,order-updatedrather than generic names. This makes the code self-documenting and allows different elements to listen for specific events. - Implement server-side heartbeats. Send a periodic comment line (
: heartbeat) in SSE or a ping frame in WebSockets to keep the connection alive through proxies and load balancers. - Scope WebSocket connections. Place the
ws-connectattribute on the smallest relevant container. When that element is removed from the DOM (e.g., by navigating away), HTMX automatically closes the connection.
Common Pitfalls
- Forgetting to load the extension. The
wsandsseextensions are not bundled with core HTMX. You must include the extension script and activate it withhx-ext="ws"orhx-ext="sse". - Sending HTML in SSE
datafields with newlines. SSEdatafields cannot contain raw newlines. Either send single-line HTML or use multipledata:lines (they are concatenated with newlines by the browser). - Not handling reconnection state. After reconnection, the client may have missed updates. Implement a strategy to resynchronize: the server can send a full state snapshot on connection, or the client can request one.
- Opening too many SSE connections. Browsers limit concurrent HTTP connections per domain (typically 6 for HTTP/1.1). Each SSE stream uses one connection. Use HTTP/2 (which multiplexes streams) or consolidate events into a single SSE endpoint.
- Memory leaks from accumulated DOM content. In a chat or live feed, messages accumulate indefinitely. Periodically trim old messages on the client side using an
htmx:afterSwaphandler or limit the initial history sent by the server.
Install this skill directly: skilldb add htmx-skills
Related Skills
Htmx Active Search
Active search, typeahead, and autocomplete patterns using HTMX triggers and debouncing
Htmx Alpine Integration
Combining Alpine.js with HTMX for client-side state and interactivity alongside hypermedia-driven updates
Htmx Backend Patterns
Server-side patterns for HTMX including partial templates, response headers, and middleware
Htmx Basics
Core HTMX attributes and fundamental patterns for hypermedia-driven web development
Htmx Forms
Form handling, validation, and submission patterns with HTMX
Htmx Infinite Scroll
Infinite scroll and lazy loading patterns using HTMX revealed and intersect triggers