Skip to main content

Choose cursor pagination for a changing activity feed

2026-11-11 16:36:24.476800+00:00
Choose cursor pagination for a changing activity feed

An offset describes a row position in the current result, not a permanent place in a feed. If new events arrive above the first page, the next offset can repeat an item. A cursor based on stable ordering is a better fit for many chronological activity feeds.

The client follows the returned cursor under a stable insertion-order policy.

Use an unchanging ordering key

For a small Django REST Framework example, a monotonically increasing primary key gives an explicit insertion-order policy:

from rest_framework.pagination import CursorPagination
from rest_framework.generics import ListAPIView

class ActivityPagination(CursorPagination):
    page_size = 20
    ordering = "-id"

class ActivityList(ListAPIView):
    serializer_class = ActivitySerializer
    pagination_class = ActivityPagination

    def get_queryset(self):
        return Activity.objects.filter(
            workspace_id=self.request.user.workspace_id
        )

This assumes a toy Activity model and the application's own authenticated workspace membership checks. Primary-key order is insertion order for this example; it is not necessarily the time a real-world event happened. Imported older events would still appear according to their new IDs.

If the product requires event-time ordering, select a suitable immutable, indexed field and understand how ties are handled. A field that users can edit is a poor cursor anchor because records can move across an already-read boundary.

Test with an insert between pages

Create enough synthetic events for two pages. Request page one and save the server-generated next link. Insert a new event, follow that link and compare IDs across the two responses. The older portion of the feed should follow the declared cursor policy without repeating the boundary record.

Follow the exact returned link rather than building a cursor from a guessed timestamp. Cursor values are part of the API's pagination contract, and clients should treat them as opaque.

Keep authorization and filters consistent across requests. A cursor cannot make a user's changing permissions disappear. If the client changes a filter, restart pagination so it does not reuse a cursor for a different result set.

Cursor pagination gives up arbitrary page-number jumps. It also does not provide a historical database snapshot: records can still be deleted or change visibility between requests. Choose it because its navigation fits the product, then test the consistency the interface actually promises.

The real-time workflows in WhatsApp Smart Connect provide related context. DRF documents cursor ordering requirements.