09

Relay vs Apollo: two answers to the same problem

Both are mature GraphQL clients with normalized caches, Suspense hooks and optimistic updates. The difference is philosophy. Apollo is a runtime library you configure. Relay puts a compiler in front of the runtime and asks your schema to follow a few conventions, and in exchange it can do work Apollo leaves to you. Compared against Relay 21 and Apollo Client 4.3; pick a row to see both sides in code.

What you get out of the box · 14 capabilities

built in, on by defaultbuilt in, opt-inseparate toolingyou write itnot available
Relay 21
9
5
Apollo Client 4
5
3
5
1
CapabilityRelayApollo

Generated pagination queries

No first-party Apollo equivalent
Relay✓on by default

@refetchable + @connection: the compiler writes the pagination query, and loadNext merges edges into the connection record.

fragment Feed_query on Query
  @refetchable(queryName: "FeedPaginationQuery") {
  feed(first: $count, after: $cursor) @connection(key: "Feed_feed") { ... }
}
const { data, loadNext } = usePaginationFragment(fragment, ref);
Apollo Client✎you write it

Call fetchMore with the right variables and register a field policy that merges pages (relayStylePagination helps).

new InMemoryCache({
  typePolicies: { Query: { fields: { feed: relayStylePagination() } } },
});
fetchMore({ variables: { after: data.feed.pageInfo.endCursor } });
where Apollo is the easier choice
  • No compiler or build step: add the client and start writing queries.
  • Works with any GraphQL schema. Relay works best when the server follows the Node interface and connection spec.
  • Easy to adopt incrementally in an existing app, one query at a time.
  • A larger ecosystem: Apollo Link middleware, the Router/GraphOS platform, plenty of tutorials.
  • Flexible cache policies (typePolicies, keyFields) for schemas without global ids.
what both do well
  • A normalized cache keyed by entity id (Relay: global id; Apollo: __typename + id or keyFields).
  • Optimistic responses that roll back on error.
  • Suspense-first hooks and query preloading before render.
  • @defer and @stream against spec-compatible servers.
  • SSR and Next.js App Router support.
  • why it mattersRelay's edge comes from knowing every query at build time: validation, types, persisted ids, generated pagination and masking all fall out of that.
  • why it mattersApollo has been closing gaps (data masking, useFragment, @defer/@stream), usually as opt-in features on top of a flexible runtime.
  • why it mattersRelay asks more of the schema (Node, connections, mutation payloads). If you control the server, that's cheap. If you don't, Apollo's flexibility matters more.