Pagination for MENA portals: cursors and snapshots
Choose live browsing or a fixed review, then design ordering, cursor scope and Arabic-English navigation around that promise.

A support manager opens a queue, reads the first page and clicks Next. A newly arrived ticket pushes an older row onto the next page, so the manager sees it twice. Another ticket disappears from the filtered queue before its turn. The interface still looks orderly, but the traversal has changed underneath it.
For Arabic-English business portals serving Saudi and UAE teams, pagination needs a product contract: is the user browsing a changing list, or reviewing a fixed set? This guide proposes a design for ordinary work queues and separates it from snapshot-based review. It does not prescribe a country-specific hosting or compliance model.
Cover: original SultanByte artwork showing the difference between a page boundary and a fixed dataset.
Choose what the reader is allowed to expect
Use a live list when the purpose is finding current work. Tell users that records can change during browsing, and offer an explicit refresh. Do not promise that reaching the end means they have inspected every record that existed when they started.
Use a fixed review set when completeness matters: a reconciliation review, for example, or a report with a defined reporting boundary. Specify whether that freezes membership only or the actual values displayed. Saving a list of IDs does not preserve the old contents of those records.
This distinction should be agreed before choosing an API shape. A cursor is a position in an ordering. A snapshot is a statement about the data being traversed. The proposed design treats them as separate responsibilities, rather than assuming one supplies the other.
For a product owner, write one acceptance sentence: "Browsing shows current permitted records" or "This review shows the captured values from this identified dataset." If the product needs both, give them visibly different modes.
Give every row an unambiguous position
PostgreSQL 18's LIMIT and OFFSET documentation requires a predictable, unique ordering for reliable subsets. It also warns that skipped rows still have to be computed, making a large offset potentially inefficient.
For the proposed chronological queue, use a non-null, immutable creation timestamp plus a unique record ID as the tie-breaker. Specify the direction of both fields. Keep their full stored precision in the server's cursor representation; a display timestamp is not an adequate substitute.
With descending order, the next request continues strictly below the last returned timestamp-and-ID pair. Do not use the timestamp alone: multiple tickets can share it. Validate the actual database query and its index against the tenant filter, supported sorts and realistic data volumes. This guide makes no latency claim for that query.
Offset pagination remains a reasonable option for a small, bounded administrative list where jumping to a page number is useful and change during browsing is acceptable. Avoid treating "cursor" as an automatic performance guarantee. The filter, index and query plan still determine the work.
For Arabic name sorting, choose a separate, explicit sort contract. Do not sort each downloaded page in the browser and present the result as a globally alphabetised list. A locale or collation change should start a new traversal when it changes ordering. SultanByte's Arabic collation guide covers that decision in more detail.
Keep live traversal separate from a snapshot
A stable ordering helps, but it cannot freeze a live query. A ticket may change status and leave the filter. A record may be deleted, or a late-arriving record may receive a sort value behind the current boundary. Decide whether those effects are acceptable rather than hiding them behind an opaque token.
PostgreSQL's transaction-isolation documentation says that successive SELECT statements at Read Committed can see different committed data, even within one transaction. A transaction around each page request therefore does not create one shared snapshot across a user's browsing session.
For fixed review, this design recommends a bounded server-side review artifact with captured values and an explicit expiry. Record its selection rules and creation boundary. Build it using a consistency strategy the team can demonstrate; independently querying several batches does not, by itself, establish a coherent capture.
Keep permission checks current when serving that artifact. Historical membership must not become permanent access. If permissions change so that the promised complete view can no longer be delivered, invalidate the review or clearly report the restriction rather than silently calling it complete.
Search engines have their own mechanisms. Elastic's pagination documentation says search_after requests reuse the query and sort, and warns that a refresh can change ordering between requests. It recommends a point in time, or PIT, to preserve the index state, and documents the associated tie-breaker behaviour. These are Elasticsearch-specific semantics, not guarantees supplied by every API named "cursor".
Original infographic: SultanByte. Proposed decision framework informed by PostgreSQL 18, Elastic, Microsoft and OWASP documentation, November 2026. It describes engineering choices, not measured performance.
Bind the cursor to the query, not just the last ID
For this portal design, the server issues a cursor that refers to the tenant, filter, sort definition, boundary values, format version and expiry. Use either a protected server-side lookup or an integrity-protected representation. Keep sensitive search terms out of a readable token when they do not need to leave the server.
Treat the authenticated request as the authority for user and workspace identity. Compare the cursor's scope with it; never let a token choose a different tenant. Recheck current access before returning rows or a total count. OWASP's authorization guidance calls for permission validation on every request, not merely the first page.
In the proposed API, a changed filter or sort starts again without the old cursor. Unknown versions, expired references and inconsistent scope receive a documented restart response. Do not silently reinterpret them as a valid position. Bound token length, page size and the amount of backend work independently.
A useful response includes the returned items, a continuation cursor and an explicit indication of whether more results were found. Microsoft's EF Core pagination guidance distinguishes keyset navigation from random access to a numbered page. It also illustrates a date-and-ID boundary and stresses matching indexes to the ordering. Decide whether the product truly needs arbitrary page jumps before committing to that additional behaviour.
For a custom REST endpoint, document your own equivalent contract. Do not promise a stable page count for a changing queue simply because the first response included an exact count at that instant.
Make Arabic-English navigation predictable
For the proposed interface, use labelled Previous and Next controls, with translated accessible names, rather than relying on arrows alone. Review their placement in the right-to-left layout with Arabic users. Keep the chronological order of the underlying records explicit, regardless of the direction in which the controls are arranged.
Separate display language from query semantics. Switching labels from English to Arabic can preserve the traversal when the data order and filter remain unchanged. Switching to an Arabic name sort cannot reuse a cursor from a different ordering. A Saudi workspace and a UAE workspace also require distinct query scope, even if the operator uses the same language in both.
On a page change, retain a usable focus position and announce the result concisely. W3C's status-message guidance distinguishes the result list itself from messages about its status, such as completion or no results. Make the status available to assistive technology without reading the entire table as one announcement.
This design keeps the current page visible while the next request runs. If it fails, show an error and a retry control without replacing the existing rows with an empty-state message. Distinguish "no matching records" from "could not load records" and "this review has expired".
Also guard against late responses. Assign a generation to the active workspace, filter and sort. When any of those changes, reject results from the previous generation even if aborting the old request did not prevent its response. A slow Saudi-workspace request must not overwrite the newly opened UAE view.
Test movement, expiry and recovery
Use the following as proposed acceptance cases, with expected outcomes agreed for each mode:
- Create several records with identical timestamps. Traverse forward and backward and check that the tie-breaker preserves the intended order.
- Insert and delete records between requests. For live mode, confirm the documented behaviour; for fixed review, require the captured dataset to remain consistent.
- Change a record's filter membership or mutable sort value. Check whether it can cross the boundary, and ensure the interface makes no stronger completeness promise than the design supports.
- Change language, sort, filter and workspace independently. Preserve position only when the query contract remains compatible.
- Expire the cursor, remove access and replay a cursor from another workspace. Require the documented safe response without disclosing protected records or counts.
- Delay a request until after the operator changes workspace. Confirm that the old response cannot replace the current screen.
For performance review, measure representative early and deep pages with production-like distributions and permission filters. Include the cost of any exact count and snapshot preparation. Keep those measurements separate from correctness: a fast endpoint can still skip work.
Finally, do not reuse a browsing cursor as approval for "all matching records". Selection and mutation need their own reviewed scope; the bulk-action guide covers that boundary. Start with a live, uniquely ordered queue for ordinary navigation, and add a bounded fixed-review mode only where the workflow needs a defensible completeness claim.
About the author
- I have lead the Engineering for multiple startups in UAE. I also have my own agency qualascend.com.
Comments
Join the discussionNo comments yet. Be the first to comment.
Builder Guides
Practical architecture, engineering, security and implementation guides for teams building technology in MENA.




