Instead of sending JSON data and letting clients figure out display, send complete HTML from the server. Real-time updates become simple DOM replacements.
Traditional: Action → JSON → Client Processing → DOM Updates
Hypermedia: Action → HTML → Direct DOM Replacement
Philosophy: Server controls all state, pushes updates via SSE. No client-side state management.
- User clicks checkbox → HTMX sends POST to server
- Server updates state → Calculates new checkbox + counter state
- Server broadcasts updates → SSE pushes HTML to all clients
- All clients update → Checkbox and counter sync in real-time
func ToggleHandler() {
// Update server state
checkboxes[id] = !checkboxes[id]
// Broadcast checkbox change
hub.Broadcast(sse.Event{
Name: fmt.Sprintf("checkbox-%d-updated", id),
Data: renderCheckboxHTML(id),
})
// Broadcast counter update
totalChecked := countChecked(checkboxes)
hub.Broadcast(sse.Event{
Name: "counter-updated",
Data: fmt.Sprintf("%d checked", totalChecked),
})
}<!-- Counter updates via SSE -->
<span sse-swap="counter-updated" hx-target="this">
{{ initialCount }} checked
</span>
<!-- Checkbox triggers server change -->
<input type="checkbox"
hx-post="/toggle/1"
hx-swap="outerHTML"
hx-target="this" />// Only for adding originator IDs to requests
document.addEventListener('htmx:configRequest', function(evt) {
evt.detail.headers['X-Originator-ID'] = window.originatorId;
});Problem: SSE elements inherit hx-target from parent hx-boost wrappers.
Solution: Always add explicit hx-target="this" to SSE elements.
<!-- ❌ Wrong: inherits parent's hx-target -->
<div sse-swap="my-event" hx-swap="innerHTML">
<!-- ✅ Correct: explicit target -->
<div sse-swap="my-event" hx-swap="innerHTML" hx-target="this">Prevents duplicate updates by excluding the action originator from SSE broadcasts.
- Server generates unique ID per page load
- SSE connection uses ID:
/events?originator={id} - HTMX requests include ID in
X-Originator-IDheader - Broadcasting excludes the originator connection
// Handle action + broadcast to others
func toggleHandler(c echo.Context) error {
originatorID := c.Request().Header.Get("X-Originator-ID")
// Update state + broadcast to all except originator
hub.Broadcast(Event{
Name: "item-updated",
Data: renderHTML(),
ExcludeID: originatorID,
})
return c.NoContent(204)
}Main Pattern: Server state + SSE updates + minimal JavaScript
SSE Fix: Always use hx-target="this"
Key Insight: HTML broadcasting eliminates client-side complexity
The server becomes the single source of truth for how things should look, turning real-time sync from a complex state management problem into simple HTML broadcasting.