htmx: Build Interactive Web Apps with HTML · Beyond the Basics

Real-Time Updates

Choose between polling, Server-Sent Events and WebSockets, and build an export job whose progress updates live.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

An export contacts job: pressing the button starts a job on the server, and a progress area updates live until the job finishes, then stops asking.

The idea

Technique How Choose it when
Polling hx-trigger="every 2s"; status 286 stops it updates can be a few seconds late; simplest to run
Server-Sent Events htmx-ext-sse: sse-connect, sse-swap the server pushes updates as they happen (one-way)
WebSockets htmx-ext-ws: ws-connect, ws-send both sides talk constantly (chat, collaboration)

Polling works through every proxy and load balancer and is easy to scale; SSE and WebSockets keep a connection open per user, which needs care with timeouts and server capacity.

The code

Start a job and poll its progressTry it yourself
<div id="export">
  <button hx-post="/exports" hx-target="#export">Export contacts (CSV)</button>
</div>

<!-- The pretend server: on a real site this part is your backend (Python, PHP, Node, Go...) -->
<script>
  let progress = 0;
  server.post("/exports", () => {
    progress = 0;   // the job starts; the response contains the element that polls
    return `<p hx-get="/exports/17" hx-trigger="every 1s">Export queued...</p>`;
  });
  server.get("/exports/17", () => {
    progress += 25;
    if (progress >= 100) return { status: 286, body: `<a href="#">contacts.csv</a> is ready.` };
    return `<progress value="${progress}" max="100"></progress> ${progress}%`;
  });
</script>

The same feature with SSE pushes each step instead of being asked:

<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<div hx-ext="sse" sse-connect="/exports/17/events" sse-swap="progress" sse-close="done">Export queued...</div>

Common mistakes

  • Polling with no stop condition: finished jobs keep generating requests. Return 286 or replace the polling element.
  • Holding database connections open per SSE/WebSocket client: use async servers or a pub/sub layer.
  • Choosing WebSockets for one-way notifications: SSE is simpler and reconnects automatically.

Key points

  • Start with polling; move to SSE when latency matters, WebSockets when traffic is two-way.
  • 286 stops htmx polling.
  • htmx 2 provides SSE and WebSockets as separate extensions.

Further reading: htmx SSE extension · htmx WebSocket extension · htmx example: progress bar

Practice questions

How does a server stop an htmx polling element?

Answer

Respond with HTTP status 286, or return content without the polling trigger.

When would you choose SSE over WebSockets?

Answer

For one-way server-to-browser updates: SSE is plain HTTP, simpler to operate and reconnects automatically.

What's the operational cost of SSE and WebSockets compared with polling?

Answer

Each client holds an open connection, so servers, proxies and load balancers must handle many long-lived connections and timeouts.

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?