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
First Request
Request the first page of results:Response includes dishes 1-20, plus
endCursor: "abc123" and hasNextPage: trueNext Page
User scrolls down. Request the next page using the cursor:Response includes dishes 21-40, plus
endCursor: "def456" and hasNextPage: true- Results — The dishes for this page
- endCursor — Pass this as
afterto get the next page - hasNextPage — Whether more results exist
Pagination Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
first | Int | 25 | Number of results per page when paginating forwards (max 100) |
after | String | — | Cursor marking where to continue from when paginating forwards |
last | Int | — | Number of results per page when paginating backwards |
before | String | — | Cursor marking where to continue from when paginating backwards |
Page Info Response
Every paginated response includes apageInfo 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:Example Response
Example Response
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?
| Issue | Offset Pagination | Cursor Pagination |
|---|---|---|
| New items added | Duplicates or missed items | Stable results |
| Items removed | Skipped items | Stable results |
| Deep pages | Slow (must skip N items) | Fast (direct lookup) |
| Real-time data | Inconsistent | Consistent |

