Append GraphQL Pages in Apollo Client InMemoryCache with typePolicies
Configure Apollo Client's InMemoryCache with keyArgs:false and a merge function to append GraphQL connection pages without refetching the first page.
05 Oct 2025, 21:30 UTC

Useful answer
To paginate a GraphQL connection in Apollo Client without refetching the first page, configure an InMemoryCache typePolicy for the connection field: set keyArgs: false so pagination arguments do not split the cache entry, and provide a merge function that concatenates the incoming edges array onto the existing one.
How the mechanism works
When Apollo Client executes a query, it looks up the cache entry for each field using the field’s keyArgs. If those arguments are part of the cache key, a new cursor value creates a new cache entry, overwriting the previous result. By setting keyArgs: false for the connection field, all queries that differ only in cursor or limit map to the same cache entry. Apollo then invokes the field’s merge function whenever the same field is read again, giving you a chance to combine the old and new data.
The merge function receives the existing cached value (or undefined on first read) and the incoming result from the server. Returning a new array that appends the incoming edges to the existing edges accumulates pages while preserving the normalized cache structure.
Worked configuration
Assume a Relay‑style connection users(first: Int, after: String): UserConnection where UserConnection has an edges field containing objects with a node (the actual User) and a cursor. Each User must expose __typename and a stable id so Apollo can normalize nodes across pages.
import { InMemoryCache } from '@apollo/client';
const cache = new InMemoryCache({
typePolicies: {
UserConnection: {
// Ignore pagination args for cache identity
keyArgs: false,
merge(existing = [], incoming) {
// Both existing and incoming are arrays of edge objects
return [...existing, ...incoming];
},
},
// Ensure User objects can be normalized
User: {
keyFields: ['id'],
},
},
});
export default cache;
With this cache in place, the query component can fetch pages sequentially:
import { gql, useQuery } from '@apollo/client';
const USERS = gql`
query Users($first: Int, $after: String) {
users(first: $first, after: $after) {
edges {
node {
id
name
}
cursor
}
pageInfo {
endCursor
hasNextPage
}
}
}
`;
function UsersList() {
const { data, fetchMore, refetch } = useQuery(USERS, {
variables: { first: 10 },
});
const loadMore = () => {
fetchMore({
variables: {
after: data.users.pageInfo.endCursor,
},
updateQuery: (prev, { fetchMoreResult }) => {
// Not needed; the cache merge already combined edges
return fetchMoreResult;
},
});
};
return (
{data?.users.edges.map(edge => (
{edge.node.name}
))}
Load more
);
}
Verifying the behavior
After the first query runs, open the browser console and execute:
// Assuming the Apollo Client instance is available as `client`
console.log(client.cache.extract());
Look for the UserConnection entry under the ROOT_QUERY. Its edges array should contain the objects from the first page only. After triggering loadMore and running the same cache.extract() again, the edges array should now hold the concatenated items from both pages, while each User object appears only once in the normalized cache (same cache ID).
You can also use Apollo Client DevTools: select the users field, view its cache payload, and verify that the edges list grows without duplication of node IDs.
Limits and common mistakes
- keyArgs: false is field‑specific – If the same connection field is used with different filter or sort arguments (e.g.,
users(first: 10, after: "abc", orderBy: NAME)vs.orderBy: CREATED_AT), settingkeyArgs: falsewill incorrectly merge those distinct result sets. In such cases, include the varying arguments inkeyArgsor create separate typePolicies for each variant. - Merge must not mutate existing – The function must return a new array; mutating the cached array can leave stale references in components and cause UI inconsistencies.
- Objects need __typename and stable id – Without
__typenameApollo cannot generate a cache key, so the merge function may never be invoked and you will see duplicate or missing entries. Verify that your GraphQL selections include__typenameon the node type. - Scalar edges cannot be normalized – If the connection returns scalar values (e.g.,
users: [String!]) there is no identifier to deduplicate. Either wrap the scalar in an object with anidor accept a non‑normalized cache shape for that field. - Cache is client‑side only – The accumulated list lives only in the InMemoryCache; a page refresh or cache garbage‑collection (if you enable eviction) will discard the merged result unless you persist the cache with
apollo3-cache-persistor similar.
Practical checklist
- Confirm the connection field’s typePolicy has
keyArgs: false. - Ensure the merge function returns a new array (
[...existing, ...incoming]). - Check that every object in the connection includes
__typenameand a stableid(or configurekeyFields). - Run the query for page 1, then for page 2 with a cursor, and inspect
client.cache.extract()to see edges from both pages. - Use Apollo Client DevTools to verify that repeated nodes share the same cache ID.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.