N

Infinite scroll

A sentinel element at the bottom of the list triggers a fragment request when it enters the viewport — appending, not replacing.

#When to use

Activity feeds, comment threads, product grids where users expect to browse without clicking. For paginated tables, see Paginated list instead — it's far more accessible for deep navigation.

#Markup

html
<ul id="feed">
    <partial name="_FeedItems" model="Model.Items" />
    @if (Model.NextCursor is not null)
    {
        <li id="feed-sentinel"
            hx-get="/feed?cursor=@Model.NextCursor"
            hx-trigger="intersect once"
            hx-target="#feed-sentinel"
            hx-swap="outerHTML">
            <noscript><a href="/feed?cursor=@Model.NextCursor">Load more</a></noscript>
        </li>
    }
</ul>
  • hx-trigger="intersect once" fires when the sentinel scrolls into view (the once modifier prevents re-firing).
  • hx-swap="outerHTML" replaces the sentinel itself with the server's response — which contains the new items PLUS a fresh sentinel pointing at the next cursor.
  • The

#Handler

csharp
public async Task<IActionResult> OnGetAsync(string? cursor = null, CancellationToken ct = default)
{
    var page = await _feed.PageAsync(cursor, pageSize: 20, ct);
    return this.PageOrPartial("_FeedItems", page);
}

#The partial that returns BOTH new items and the next sentinel

cshtml
@model FeedPage
@foreach (var item in Model.Items)
{
    <li>@item.Title</li>
}
@if (Model.NextCursor is not null)
{
    <li id="feed-sentinel"
        hx-get="/feed?cursor=@Model.NextCursor"
        hx-trigger="intersect once"
        hx-target="#feed-sentinel"
        hx-swap="outerHTML">
        <noscript><a href="/feed?cursor=@Model.NextCursor">Load more</a></noscript>
    </li>
}

Each fetch replaces the old sentinel with new items + a new sentinel. When the server runs out of pages it omits the sentinel and the chain stops naturally.

#Cursor vs offset

Prefer opaque cursors (e.g. the last item's id encoded base64) over numeric offsets. Offsets break when new items arrive between requests; cursors stay stable.