Recipe: Cursor-based pagination design
Replace offset/limit with stable cursors for predictable, performant list traversal.
Why cursors
Offset pagination drifts when rows are inserted or deleted between requests. Cursors anchor each page to a stable sort key, so the client always sees a consistent slice — no duplicates, no gaps.
Cursor shape
Encode the last-seen sort value into an opaque base64 token. For timestamp-ordered lists use created_at plus a tie-breaker like id. Never expose internal row offsets.
Request / response
Clients send ?cursor=<token>&limit=50. The server decodes the cursor, applies a WHERE clause on the sort key, and returns the next batch plus a next_cursor field. Omit next_cursor when the batch is smaller than limit to signal end-of-list.
Forward-only by default
Bidirectional cursors add complexity. Start with forward-only pagination. If reverse traversal is needed later, add a prev_cursor and flip the sort direction.
Meridian tip: Store cursors as HMAC-signed tokens to prevent clients from tampering with sort values. Validate the signature on every request before decoding.