Apollo 23: Offline Support
easy⏱ 5 mincourseapollo
Offline-first strategy
An offline-first app works without a network connection by:
- Persisting the cache to localStorage or IndexedDB via
apollo3-cache-persist. On app load, the cache is restored from storage before any network request.
- Optimistic mutations update the cache immediately when offline. The user sees the change instantly.
- A retry queue stores failed mutations and replays them when the network is restored.
- Conflict resolution handles cases where the server state diverged while offline.
This architecture gives users a seamless experience — the app feels fast and responsive even with poor connectivity.
import { persistCache, LocalStorageWrapper } from 'apollo3-cache-persist';
const cache = new InMemoryCache();
// Restore cache from storage before creating the client
await persistCache({
cache,
storage: new LocalStorageWrapper(window.localStorage),
maxSize: 1048576, // 1MB
debug: true,
});
const client = new ApolloClient({
link: httpLink,
cache, // Pre-populated from localStorage
});
Add cache persistence
Install apollo3-cache-persist and call persistCache({ cache, storage: new LocalStorageWrapper(window.localStorage) }) before creating your Apollo Client. Wrap the client creation in an async function and use a loading state while the cache restores. Verify persistence by fetching data, refreshing the page, and observing that data appears instantly before any network request. Add maxSize: 1048576 to limit storage to 1MB.
Conflict resolution strategies
When the user makes changes offline and the server data has also changed, you need a conflict resolution strategy:
- Last write wins: The most recent mutation overwrites. Simple but can lose data.
- Server wins: Always accept the server's version. Safe but discards offline changes.
- Client wins: Always accept the client's version. Dangerous for shared data.
- Merge: Combine changes field by field. Complex but preserves all data.
- User decides: Show a conflict UI and let the user choose.
For most apps, last write wins with timestamps is sufficient. For collaborative apps, use operational transforms or CRDTs.
// Detect online/offline status
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
Offline mutation queue
When mutations fail due to network errors, store them in a persistent queue (IndexedDB or localStorage). Each entry contains the mutation document, variables, and optimistic response. When the app detects connectivity (navigator.onLine or a successful health check), replay the queue in order. Use RetryLink for automatic retry on network errors, or build a custom queue for more control. Always apply optimistic responses immediately so the UI stays responsive while offline.