Skip to main content

Why Cursor-Based Pagination

Traditional offset pagination (page=2&limit=20) breaks down at scale. When a guest is browsing page 5 and a new dish gets added, suddenly they see duplicates or miss items entirely. For a dining app where menu availability changes in real-time, this creates a frustrating experience. EveryBite uses cursor-based pagination—the same approach used by Twitter, Facebook, and Slack. Instead of “give me page 2”, you say “give me the next 20 items after this cursor.” The cursor is an opaque marker that points to a specific position in the result set, ensuring stable pagination even as data changes.

How It Works

1

First Request

Request the first page of results:
Response includes dishes 1-20, plus endCursor: "abc123" and hasNextPage: true
2

Next Page

User scrolls down. Request the next page using the cursor:
Response includes dishes 21-40, plus endCursor: "def456" and hasNextPage: true
3

Final Page

User scrolls again. Request the next page:
Response includes dishes 41-52 and hasNextPage: false — no more results.
Each response includes:
  • Results — The dishes for this page
  • endCursor — Pass this as after to get the next page
  • hasNextPage — Whether more results exist

Pagination Parameters

Page Info Response

Every paginated response includes a pageInfo object:

Example: Infinite Scroll

The most common pattern—load more results as the user scrolls:

Example: Load More Button

For explicit pagination with a “Load More” button:

Pagination with Match Groups

When paginating search results, pagination applies across all match groups:
Results are returned in match quality order: all matches first, then almostMatches, then notMatches. Pagination respects this ordering.

Cursor Stability

Cursors remain valid for the duration of a session. However:
  • Cursors are opaque — Don’t parse or construct them; treat as strings
  • Cursors are session-scoped — A cursor from one session won’t work in another
  • Cursors expire — After session timeout (30 min inactivity), start fresh

Best Practices

Request Reasonable Pages

20-50 items per page balances performance and UX. Avoid requesting 100 items if you only show 10.

Don't Store Cursors

Cursors are ephemeral. Don’t persist them to local storage or databases.

Handle Empty Pages

If hasNextPage is false and results are empty, show an appropriate empty state.

Show Total Count

Use counts.total to show “Showing 20 of 52 dishes” for better UX.

Why Not Offset Pagination?

For a menu that can change in real-time (dishes sold out, specials added), cursor pagination ensures guests see a consistent, complete list.