| name | apollo-caching-strategies |
| description | Use when implementing Apollo caching strategies including cache policies, optimistic UI, cache updates, and normalization. |
| allowed-tools | ["Read","Write","Edit","Grep","Glob","Bash"] |
Apollo Caching Strategies
Master Apollo Client's caching mechanisms for building performant applications
with optimal data fetching and state management strategies.
Overview
Apollo Client's intelligent cache is a normalized, in-memory data store that
allows for efficient data fetching and updates. Understanding cache policies
and management strategies is crucial for building high-performance apps.
Installation and Setup
Cache Configuration
import { InMemoryCache, makeVar } from '@apollo/client';
export const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
posts: {
keyArgs: ['filter'],
merge(existing = [], incoming, { args }) {
const merged = existing.slice(0);
const offset = args?.offset || 0;
for (let i = 0; i < incoming.length; i++) {
merged[offset + i] = incoming[i];
}
return merged;
}
}
}
},
Post: {
keyFields: ['id'],
fields: {
comments: {
merge(existing = [], incoming) {
return [...existing, ...incoming];
}
}
}
},
User: {
keyFields: ['email'],
fields: {
fullName: {
read(_, { readField }) {
return `${readField('firstName')} ${readField('lastName')}`;
}
}
}
}
}
});
Core Patterns
1. Fetch Policies
import { useQuery } from '@apollo/client';
import { GET_POSTS } from './queries';
function CacheFirstPosts() {
const { data } = useQuery(GET_POSTS, {
fetchPolicy: 'cache-first'
});
return <PostsList posts={data?.posts} />;
}
function CacheOnlyPosts() {
const { data } = useQuery(GET_POSTS, {
fetchPolicy: 'cache-only'
});
return <PostsList posts={data?.posts} />;
}
function CacheAndNetworkPosts() {
const { data, loading, networkStatus } = useQuery(GET_POSTS, {
fetchPolicy: 'cache-and-network',
notifyOnNetworkStatusChange: true
});
(
);
}
() {
{ data } = (, {
:
});
;
}
() {
{ data } = (, {
:
});
;
}
() {
{ data, refetch } = (, {
:
});
(
);
}
2. Cache Reads and Writes
import { gql } from '@apollo/client';
export function readPostFromCache(client, postId) {
try {
const data = client.readQuery({
query: gql`
query GetPost($id: ID!) {
post(id: $id) {
id
title
body
}
}
`,
variables: { id: postId }
});
return data?.post;
} catch (error) {
console.error('Post not in cache:', error);
return null;
}
}
export function writePostToCache(client, post) {
client.writeQuery({
query: gql`
query GetPost($id: ID!) {
post( )
id
title
body
`,
: { : post. },
: { post }
});
}
() {
client.({
: ,
: gql`
});
}
() {
client.({
: ,
: gql`,
: {
likesCount
}
});
}
() {
client..({
: client..({ : , : postId }),
: {
() {
currentCount + ;
},
() {
;
}
}
});
}
3. Optimistic Updates
import { useMutation } from '@apollo/client';
import { LIKE_POST } from '../mutations';
function OptimisticLike({ post }) {
const [likePost] = useMutation(LIKE_POST, {
variables: { postId: post.id },
optimisticResponse: {
__typename: 'Mutation',
likePost: {
__typename: 'Post',
id: post.id,
likesCount: post.likesCount + 1,
isLiked: true
}
},
update(cache, { data: { likePost } }) {
cache.modify({
id: cache.identify(post),
fields: {
likesCount() {
return likePost.likesCount;
},
isLiked() {
return likePost.isLiked;
}
}
});
},
onError() {
.(, error);
}
});
(
);
}
() {
[createComment] = (, {
: ({
: ,
: {
: ,
: ,
body,
: ().(),
: {
: ,
: currentUser.,
: currentUser.,
: currentUser.
}
}
}),
() {
cache.({
: cache.({ : , : postId }),
: {
() {
newCommentRef = cache.({
: createComment,
: gql`
});
[...existing, newCommentRef];
},
() {
count + ;
}
}
});
}
});
;
}
4. Cache Eviction
export function evictPost(client, postId) {
client.cache.evict({
id: client.cache.identify({ __typename: 'Post', id: postId })
});
client.cache.gc();
}
export function evictField(client, postId, fieldName) {
client.cache.evict({
id: client.cache.identify({ __typename: 'Post', id: postId }),
fieldName
});
}
export function evictAllPosts(client) {
client.cache.modify({
fields: {
posts(existing, { DELETE }) {
return DELETE;
}
}
});
client.cache.gc();
}
function DeletePost({ postId }) {
const [deletePost] = (, {
: { : postId },
() {
cache.({
: {
() {
existingPosts.(
postId !== (, ref)
);
}
}
});
cache.({ : cache.({ : , : postId }) });
cache.();
}
});
;
}
5. Reactive Variables
import { makeVar, useReactiveVar } from '@apollo/client';
export const cartItemsVar = makeVar([]);
export const themeVar = makeVar('light');
export const isModalOpenVar = makeVar(false);
export const notificationsVar = makeVar([]);
export function addToCart(item) {
const cart = cartItemsVar();
cartItemsVar([...cart, item]);
}
export function removeFromCart(itemId) {
const cart = cartItemsVar();
cartItemsVar(cart.filter(item => item.id !== itemId));
}
export function clearCart() {
cartItemsVar([]);
}
export function toggleTheme() {
const current = themeVar();
themeVar(current === ? : );
}
() {
notifications = ();
([...notifications, {
: .(),
...notification
}]);
}
() {
cartItems = (cartItemsVar);
(
);
}
cache = ({
: {
: {
: {
: {
() {
();
}
},
: {
() {
();
}
}
}
}
}
});
6. Pagination Strategies
const POSTS_QUERY = gql`
query GetPosts($limit: Int!, $offset: Int!) {
posts(limit: $limit, offset: $offset) {
id
title
body
}
}
`;
function OffsetPagination() {
const { data, fetchMore } = useQuery(POSTS_QUERY, {
variables: { limit: 10, offset: 0 }
});
return (
<div>
<PostsList posts={data?.posts} />
<button
onClick={() =>
fetchMore({
variables: { offset: data.posts.length }
})
}
>
Load More
</button>
</div>
);
}
const CURSOR_POSTS_QUERY = gql`
query GetPosts: Int, : String
posts , )
edges
cursor
node
id
title
body
pageInfo
hasNextPage
endCursor
`;
() {
{ data, fetchMore } = (, {
: { : }
});
(
);
}
cache = ({
: {
: {
: {
: {
: [],
() {
(!existing) incoming;
{ offset = } = args;
merged = existing.();
( i = ; i < incoming.; i++) {
merged[offset + i] = incoming[i];
}
merged;
}
}
}
}
}
});
{ offsetLimitPagination } ;
cache = ({
: {
: {
: {
: ()
}
}
}
});
7. Cache Persistence
import { InMemoryCache } from '@apollo/client';
import { persistCache, LocalStorageWrapper } from 'apollo3-cache-persist';
export async function createPersistedCache() {
const cache = new InMemoryCache({
typePolicies: {
}
});
await persistCache({
cache,
storage: new LocalStorageWrapper(window.localStorage),
maxSize: 1048576,
debug: true,
trigger: 'write',
});
return cache;
}
import { ApolloClient } from '@apollo/client';
async function initApollo() {
const cache = await createPersistedCache();
const client = new ApolloClient({
uri: ,
cache
});
client;
}
() {
client.();
.();
}
cache = ({
: {
: {
: {
: {
() {
;
}
}
}
}
}
});
8. Cache Warming
import { gql } from '@apollo/client';
export async function warmCache(client) {
await Promise.all([
client.query({
query: gql`
query GetCurrentUser {
me {
id
name
email
}
}
`
}),
client.query({
query: gql`
query GetRecentPosts {
posts(limit: 20) {
id
title
excerpt
}
}
`
})
]);
}
function PostLink({ postId }) {
const client = useApolloClient();
const prefetch = () => {
client.query({
query: GET_POST,
variables: { id: postId }
});
};
return (
);
}
9. Cache Redirects
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
post: {
read(_, { args, toReference }) {
return toReference({
__typename: 'Post',
id: args.id
});
}
}
}
},
User: {
fields: {
fullName: {
read(_, { readField }) {
const firstName = readField('firstName');
const lastName = readField('lastName');
return `${firstName} ${lastName}`;
}
},
posts: {
read(existing, { args, readField }) {
if (args?.published !== undefined) {
return existing?.filter(ref =>
readField('published', ref) === args.published
);
}
existing;
}
}
}
}
}
});
10. Cache Monitoring and Debugging
export function logCacheContents(client) {
const cache = client.extract();
console.log('Cache contents:', cache);
}
export function watchCacheChanges(client) {
const observer = client.cache.watch({
query: gql`
query GetAllData {
posts {
id
title
}
}
`,
callback: (data) => {
console.log('Cache changed:', data);
}
});
return observer;
}
if (process.env.NODE_ENV === 'development') {
window.apolloClient = client;
window.logCache = () => logCacheContents(client);
setInterval(() => {
const cacheSize = JSON.stringify(client.extract()).;
.();
}, );
}
{ } ;
{ } ;
() {
(
);
}
() {
client = ();
[cacheData, setCacheData] = ({});
( {
data = client.();
(data);
}, [client]);
(
);
}
Best Practices
- Choose appropriate fetch policies - Match policy to data freshness needs
- Use optimistic updates - Improve perceived performance
- Normalize cache properly - Configure keyFields correctly
- Implement pagination - Handle large datasets efficiently
- Persist critical data - Cache auth state and user preferences
- Monitor cache size - Prevent memory bloat
- Use reactive variables - Manage local state efficiently
- Warm cache strategically - Prefetch critical data
- Evict unused data - Clean up after deletions
- Debug cache issues - Use Apollo DevTools effectively
Common Pitfalls
- Wrong fetch policy - Using cache-first for real-time data
- Cache denormalization - Missing or incorrect keyFields
- Memory leaks - Not evicting deleted items
- Over-caching - Caching too much data
- Stale data - Not invalidating cache properly
- Missing updates - Forgetting to update cache after mutations
- Incorrect merges - Wrong pagination merge logic
- Cache thrashing - Too many cache writes
- Persistence issues - Storing sensitive data
- No error handling - Not handling cache read failures
When to Use
- Building data-intensive applications
- Implementing offline-first features
- Creating real-time collaborative apps
- Developing mobile applications
- Building e-commerce platforms
- Creating social media applications
- Implementing complex state management
- Developing admin dashboards
- Building content management systems
- Creating analytics applications
Resources