Key insights
- Offset pagination drifts when the data changes — insert a row at the top and every page shifts down, so the same item can surface twice (or get skipped entirely).
- Cursor pagination stays stable: it anchors to a specific row instead of a numeric position, so inserts and deletes never create duplicates.
- Three patterns fit different jobs — numbered for jumping to any page, load-more for on-demand appends, infinite scroll for continuous feeds.
- Never render every page link. Truncate to first, last, current, and its immediate neighbors, using an ellipsis for the gaps.
- Keep the page number in the URL (
?page=500) so a refresh stays put and the view becomes a shareable link. - When users return from a detail view, restore their scroll position instead of dumping them back at the top of the list.
Do / Don't
- Do: Reach for cursor pagination when rows are inserted or deleted often, to avoid duplicates and skips
- Do: Persist the current page in the URL so refreshes and shared links land on the same page
- Do: Collapse long page ranges to first, last, current, and neighbors with an ellipsis
- Don't: Render hundreds of numbered links — they spill off-screen and overwhelm
- Don't: Reset an infinite-scroll list to the top when the user comes back from a detail view