Polling for progress
A region polls a URL on an interval until the returned status says "done". Perfect for build status, export progress, job queues.
#When to use
Background operations where the user is watching. If the server can push events (SSE / WebSocket), prefer that — see Real-time "is typing". Polling is the right tool when push isn't available.
#Markup
<div id="job_@Model.JobId"
@(Model.Status is "done" or "failed"
? ""
: "hx-get=\"/jobs/" + Model.JobId + "?partial=status\" hx-trigger=\"every 2s\" hx-swap=\"outerHTML\"")>
<p>Status: @Model.Status</p>
@if (Model.Progress is not null)
{
<progress value="@Model.Progress" max="100"></progress>
}
</div>
While the job is running, the partial emits hx-trigger="every 2s". Htmx fires a request on that interval and swaps the whole region with the response. When the partial's status is done or failed, the next render omits the polling attributes — htmx stops on its own.
#Handler
public async Task<IActionResult> OnGetAsync(int id, string? partial = null)
{
var job = await _jobs.GetAsync(id);
if (partial == "status" || Request.IsHtmx())
{
return this.PageOrPartial("_JobStatus", job);
}
return Page();
}
#Backoff
For jobs that might take minutes: start at 2s, double up to 30s, cap there. Compute the next interval in the model and emit it directly:
hx-trigger="every @(Model.NextPollMs)ms"
Each response carries a longer interval until the cap is hit.
#Stopping early
Server-side, return Content(string.Empty, "text/html") plus HX-Trigger: jobDone to (a) clear the polling region and (b) emit a DOM event other widgets can listen to.
#Related
- Infinite scroll — the same
hx-triggerprimitive used as a viewport intersect. - Real-time "is typing" — push-based alternative when you control the server.