Hey dev! If you’ve ever felt the frustration of watching a component "flicker" white or take precious seconds to load info the user has already seen, you know that state management is no joke.
Imagine you're building a financial dashboard. The user clicks on their profile, goes back home, checks settings... If every single click triggers a full request to the server, you're not just burning processing power - you're delivering a clunky experience. This is exactly why Apollo Client wasn't just born as a "fetcher," but as a powerful caching engine.
Today, we’re getting straight to the point: mastering Apollo Fetch Policies so you can decide exactly when to hit the cache and when to knock on the server’s door.
The Heart of Performance: Apollo's Cache
Apollo Client doesn't just fetch data; it normalizes and stores it. When you run a query, Apollo creates a local map. If you request that same data again, Apollo decides - based on your configuration - whether to go to the network or hand over what it already has instantly.
1. cache-first (The Smart Default)
This is the default policy. Apollo looks at the cache: "Do I have this?" If yes, it returns it and never touches the network. If no, it fetches from the server and saves it for next time.
- Use Case: Product category lists that rarely change.
- Practical Example:
// Perfect for a list of tags the user already loaded when opening the app
const { data, loading } = useQuery(GET_CATEGORIES, {
fetchPolicy: 'cache-first',
});
2. network-only (Maximum Consistency)
Here, the cache is ignored for reading. Apollo always hits the server for the latest version. Note: It still saves the result to the cache for future queries using cache-first.
- Use Case: Bank balances or real-time transaction statuses.
- Practical Example:
// Ensures the user sees the updated balance immediately after a transfer
const { data, loading } = useQuery(GET_USER_BALANCE, {
fetchPolicy: 'network-only',
});
3. cache-only (Offline/Instant Mode)
In this policy, Apollo will never make a network request. If the data isn't in the cache, it returns an error or undefined.
- Use Case: Secondary components that depend on data already loaded by a "parent" or the Home screen.
- Practical Example:
// Displaying a username in a sidebar, assuming the login process already loaded this
const { data } = useQuery(GET_USER_BASIC_INFO, {
fetchPolicy: 'cache-only',
});
4. no-cache (Privacy & Ephemerality)
Similar to network-only, it always goes to the server. The big difference? It doesn't store the result. The data disappears as soon as the component unmounts.
- Use Case: Sensitive compliance data (GDPR) or temporary logs that shouldn't persist in memory.
- Practical Example:
// Fetching audit logs that you don't want floating around in browser memory
const { data } = useQuery(GET_SENSITIVE_LOGS, {
fetchPolicy: 'no-cache',
});
Quick Comparison
| Policy | Data Source | Saves to Cache? | Primary Goal |
|---|---|---|---|
| cache-first | Cache (or Network if empty) | Yes | Loading Speed |
| network-only | Always Network | Yes | Data Consistency |
| cache-only | Always Cache | N/A | Zero Latency / Offline |
| no-cache | Always Network | No | Security & Memory Savings |
Conclusion
Setting the right fetchPolicy is what separates an amateur React app from a high-performance professional one. There is no single "best" policy - only the right policy for your user's specific flow. Balancing server load with cache speed is the secret to a fluid UX.
Did you find this technical tip helpful? If you want to see how to configure next-fetch-policy to handle background updates while showing cached data, drop a comment below!
Have you ever struggled with "stale data" using the default cache? Let me know how you handled it!



Top comments (0)