Merge paginated GraphQL pages in Apollo Client 3
Configure Apollo Client 3's InMemoryCache to merge paginated GraphQL pages instead of replacing them, with fetch-policy trade-offs and verification steps.
21 Jun 2026, 09:44 UTC

The problem: page two replaces page one
You load the first page of a feed, then click "Next." The second page arrives from the server, but the first page's items disappear from the UI. Apollo Client's default normalized cache treats each paginated query as a separate cache entry, so page 2 replaces page 1 instead of joining it.
The useful takeaway: configure the Query.feed field with a shared cache slot and a merge function so incoming pages append to existing items. Pair it with a fetch policy that matches whether your UI can tolerate stale reads.
Why the default cache loses pages
Apollo Client 3 normalizes cached data by __typename and ID. Individual entities such as Item get their own cache slots, but a list field such as feed does not. By default, each unique combination of query arguments creates a separate entry. When you query feed(cursor: "abc"), Apollo writes that result under its own key. The earlier feed(cursor: null) entry remains untouched, and the UI renders only what the current query returns.
Check your Apollo Client version
Run this in your project root:
npm list @apollo/client
No special permissions are required. Expect output showing @apollo/client@3.x.x. The typePolicies API below applies to Apollo Client 3; version 2 uses a different cache API.
Configure a shared feed slot
In Apollo Client 3, typePolicies customize how a field is cached. Setting keyArgs: false tells Apollo that every invocation of feed shares the same cache slot, regardless of the cursor argument. The merge function runs whenever the server returns a new result for that slot.
import { InMemoryCache } from '@apollo/client';
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
feed: {
keyArgs: false,
merge(existing = { items: [] }, incoming = { items: [] }) {
return {
...incoming,
items: [...existing.items, ...incoming.items],
};
},
},
},
},
},
});
If your schema returns a bare list such as [Item] instead of an object with an items field, make merge return [...(existing ?? []), ...(incoming ?? [])].
Choose a fetch policy
The fetch policy decides whether Apollo reads from cache, hits the network, or both.
| Policy | First render | Network behavior | Best for |
|---|---|---|---|
| cache-first | Cache if present, otherwise network | No request on a cache hit | Read-heavy feeds where slight staleness is acceptable |
| cache-and-network | Cache immediately | Background request after every cached read | UIs that must show something instantly and still reconcile with the server |
cache-and-network increases server traffic because every cached read is followed by a background fetch. It can also cause a brief UI update when the background result merges into the list.
Verify the merge
- Open Apollo Client DevTools, inspect the cache, locate
Query.feed, and confirm theitemsarray grows as you navigate pages. - In the browser Network tab, repeat an identical query. With
cache-first, expect no repeated HTTP request. Withcache-and-network, expect a background request after the cached render. - Write a unit test with MockLink that runs the same paginated query twice and asserts that the second execution reads from cache and the merged list contains both pages.
Limitations
- A mutation that creates or deletes an item does not automatically update the cached
feedlist. UsewriteQuery,writeFragment, or refetch the query after the mutation. - A simple append can leave duplicates or removed items in the list. Your merge function may need to deduplicate by
idor filter out deleted IDs. - Handle
nullor missing cursor values consistently so the shared cache slot behaves predictably.
Closing
Start with cache-first and keyArgs: false. Add cache-and-network only if your product requires instant cached renders and your server can handle the extra traffic. Verify with Apollo Client DevTools and the Network tab before shipping.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.