Appearance
Pagination Explained
When you're dealing with thousands of products and hundreds of stores, you can't just fetch everything at once. Pagination lets you fetch data in chunks.
LCBO.dev uses cursor-based pagination (the Relay spec). Here's what you need to know.
Why Cursors Instead of Page Numbers
Most REST APIs use offset pagination - you ask for "page 2" or "skip 20, take 20". Simple, but it breaks down:
- If new data gets added while you're paginating, you see duplicates
- If data gets removed, you skip items
Cursors fix this. Instead of "give me items 20-40", you say "give me items after this point". The cursor marks a stable position in the dataset, so even if data changes around you, you won't see duplicates or miss items.
The tradeoff: you can't jump directly to page 47. You have to page through sequentially. For most apps (infinite scroll, "load more" buttons), this isn't a problem.
The Connection Pattern
Every paginated query returns a "connection" with three parts:
edges - Array of results. Each edge has:
node- The actual item (product, store, etc.)cursor- An opaque string marking this item's position
pageInfo - Tells you where you are:
hasNextPage- Are there more items after this page?hasPreviousPage- Are there items before this page?endCursor- Pass this asafterto get the next pagestartCursor- Pass this asbeforeto get the previous page
totalCount - Total items matching your query (across all pages). Handy for showing "1-20 of 1,847 results".
How to Paginate
Forward: Use first to set page size, after to continue from where you left off. Check hasNextPage to know when you're done.
Backward: Use last and before instead. Check hasPreviousPage.
When filters change, reset your cursor - you're starting a new result set.
Page Sizes
Recommended sizes:
- Product lists: 20-50
- Search results: 15-25
- Store locators: 10-25
- Dropdowns: 50-100
Maximum is 100 items per request.
About Those Cursors
Cursors look like Y3Vyc29yOjIw - that's just base64. Don't try to decode or construct them yourself. The format could change anytime. Just pass them back exactly as you received them.
If a cursor becomes invalid (maybe the underlying data was deleted), the API returns an error. Handle it by resetting to page 1.
Can't I Just Jump to Page 50?
Not directly. Cursor pagination optimizes for sequential access (infinite scroll, next/prev) over random access.
If you need page numbers in your UI, store cursors as users navigate forward. Use totalCount with your page size to calculate total pages. Or consider whether you actually need that UX - most users browse sequentially anyway.
Quick Reference
| Forward | Backward |
|---|---|
first: 20 | last: 20 |
after: endCursor | before: startCursor |
Check hasNextPage | Check hasPreviousPage |
Next Steps
- Build a Product Finder - Pagination in practice
- Product Discovery - Search and filter with pagination
- GraphQL Concepts - More on the connection pattern