Returning every row of a large table in one response is slow for the server, heavy on the network, and useless to a client that renders twenty rows at a time. Pagination splits the collection into pages, and the response tells the client how to get the next one.
JSON:API reserves the page query parameter family and asks servers to return
links for navigation:
GET /books?page[number]=2&page[size]=20 {
"data": [
{ "type": "books", "id": "21", "attributes": { "title": "The Hobbit" } },
{ "type": "books", "id": "22", "attributes": { "title": "Dune" } },
{ "type": "books", "id": "23", "attributes": { "title": "1984" } }
],
"links": {
"next": "/books?page[number]=3&page[size]=20",
"prev": "/books?page[number]=1&page[size]=20"
}
}
Clients follow the links instead of constructing page URLs themselves, so the server
is free to change its pagination strategy without breaking anyone. And there are two strategies to
choose from.
Offset/limit vs. cursor-based
Offset/limit is the classic approach: skip N rows, take the next M
(?offset=40&limit=20, or JSON:API's page[number]/page[size]).
- Simple to implement — it maps directly to SQL's
OFFSET/LIMIT - Supports jumping to an arbitrary page ("go to page 9")
- Drifts under writes: if a row is inserted while a user is on page 2, page 3 starts one row earlier than expected — they see a duplicate or miss a row
- Slow at depth:
OFFSET 100000makes the database walk and discard 100,000 rows before returning any
Cursor-based pagination replaces the page number with an opaque token pointing at the last item the client saw: "give me 20 books after this one."
GET /books?page[size]=20&page[after]=eyJpZCI6NDJ9 - Stable under writes: the cursor anchors to a row, not a position, so inserts and deletes can't shift the window
- Fast at any depth: the database seeks straight to the cursor via an index (
WHERE id > 42), no rows discarded - No random access — you can't jump to page 9, only walk forward (and sometimes backward)
Rule of thumb: offset/limit for admin tables and small datasets where "jump to page" matters; cursors for infinite scroll, feeds, and anything large or frequently written.
Check your understanding
Check your understanding
A user scrolling a busy feed sees the same post twice at a page boundary. Which pagination strategy is the API using, and why?
Check your understanding