N

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

html
<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

csharp
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:

cshtml
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.