GraphQL in Production · Performance and Caching

Pagination with Relay Connections

Cursor-based pagination conventions most GraphQL clients understand.

The Relay Cursor Connections specification standardizes list pagination: fields take first/after (and last/before) and return a Connection with edges { cursor node } and pageInfo { hasNextPage endCursor }. Edges can carry relationship data (e.g., role on a membership). Cursors are opaque (encode keyset values), and every list must enforce a maximum first. Many clients (Relay, Apollo, urql) provide helpers for merging connection pages.

List patterns in GraphQL

Criterion Relay connections Offset args (limit/offset) Simple list
Stable with inserts Yes No N/A
Client library support Excellent Manual N/A
Edge metadata Yes No No
Verbosity Higher Low Lowest
type OrderConnection { edges: [OrderEdge!]!  pageInfo: PageInfo!  totalCount: Int }
type OrderEdge { cursor: String!  node: Order! }
type PageInfo { hasNextPage: Boolean!  hasPreviousPage: Boolean!  startCursor: String  endCursor: String }

type Query { orders(first: Int = 20, after: String, filter: OrderFilter): OrderConnection! }

query { orders(first: 20, after: "b3JkXzAxSjla…") {
  edges { cursor node { id status } }
  pageInfo { hasNextPage endCursor }
} }

Common pitfalls

  • Lists without first limits.
  • Exposing raw offsets as cursors.
  • Expensive totalCount computed for every request.

Key points

  • edges/node/cursor + pageInfo
  • Opaque keyset cursors
  • Cap first/last

Further reading: Relay Cursor Connections Specification

Practice questions

Why put a cursor on each edge instead of only in pageInfo?

Answer

Clients can resume from any item (e.g., after deleting items or for 'load more from here'), not just from the end of the page.

What kind of data belongs on an edge rather than the node?

Answer

Data about the relationship, such as a member's role in a team or when a user starred a repository.

Open in the app to mark lessons complete, take notes and track your progress.