Concepts to Know

Data Fetching Strategies

Master network request patterns for optimal performance and user experience

Choosing the right data fetching strategy can make or break your app's performance and user experience. Whether you're building a real-time dashboard or a static blog, each approach has its own trade-offs. Let's break down the three primary strategies—Network Only, Network + Cache, and Cache Only—in a practical and friendly way.


Network Only

Always fetch data straight from the server.

Pros:

  • Always Fresh: You get the most up-to-date info, straight from the source
  • Great for Real-Time: Ideal for dashboards, live stats, or anything time-sensitive
  • Simple Implementation: No cache management complexity

Cons:

  • Slower Load: Network latency might slow things down
  • No Offline Support: If the network's down, your app's data is too
  • Higher Server Load: Every request hits your backend

Implementation Example

// Simple network-only fetch
async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      throw new Error('Failed to fetch user data');
    }
    return await response.json();
  } catch (error) {
    console.error('Network error:', error);
    throw error;
  }
}

// React component example
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function loadUser() {
      try {
        setLoading(true);
        const userData = await fetchUserData(userId);
        setUser(userData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    loadUser();
  }, [userId]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!user) return <div>No user found</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Network + Cache (Stale-While-Revalidate)

Use both the cache and the network together, often in a "stale-while-revalidate" style.

Pros:

  • Best of Both Worlds: Instant response from cache + background update from network
  • Smooth UX: Feels fast and fresh when done right
  • Offline Resilience: Can work with cached data when offline

Cons:

  • Can Be Tricky: Managing when to show cached data vs. fresh data can get complex
  • Stale Risk: Users may briefly see outdated content
  • More Complex Implementation: Requires cache management logic

Implementation Example

// Cache with network fallback
class DataFetcher {
  constructor() {
    this.cache = new Map();
  }

  async fetchWithCache(url, options = {}) {
    const cacheKey = `${url}-${JSON.stringify(options)}`;
    const cached = this.cache.get(cacheKey);
    
    // Return cached data immediately if available
    if (cached && Date.now() - cached.timestamp < (options.maxAge || 5 * 60 * 1000)) {
      return cached.data;
    }

    try {
      // Fetch fresh data
      const response = await fetch(url, options);
      if (!response.ok) throw new Error('Network error');
      
      const data = await response.json();
      
      // Update cache
      this.cache.set(cacheKey, {
        data,
        timestamp: Date.now()
      });

      return data;
    } catch (error) {
      // Fallback to stale cache if available
      if (cached) {
        console.warn('Using stale cache due to network error');
        return cached.data;
      }
      throw error;
    }
  }
}

// React Query example (recommended approach)
import { useQuery } from '@tanstack/react-query';

function UserProfile({ userId }) {
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUserData(userId),
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

Cache Only

Pull data only from local storage or in-memory cache.

Pros:

  • Super Fast: Lightning-quick responses without hitting the network
  • Works Offline: Perfect for PWAs and apps with predictable data
  • Reduces Server Load: No unnecessary network requests

Cons:

  • Staleness Danger: Without sync, you risk showing old or inaccurate info
  • No Fallback: If it's not in the cache, there's no data
  • Storage Limitations: Limited by browser storage capacity

Implementation Example

// Cache-only implementation
class CacheOnlyFetcher {
  constructor() {
    this.cache = new Map();
  }

  async get(key) {
    const cached = this.cache.get(key);
    if (!cached) {
      throw new Error('Data not found in cache');
    }
    return cached.data;
  }

  set(key, data) {
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
  }

  // Pre-populate cache (usually done at app startup)
  async preloadData() {
    const staticData = await fetch('/api/static-data');
    const data = await staticData.json();
    
    // Cache all static data
    Object.entries(data).forEach(([key, value]) => {
      this.set(key, value);
    });
  }
}

// React component example
function StaticContent({ contentKey }) {
  const [content, setContent] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    try {
      const cachedContent = cacheOnlyFetcher.get(contentKey);
      setContent(cachedContent);
    } catch (err) {
      setError('Content not available offline');
    }
  }, [contentKey]);

  if (error) return <div>{error}</div>;
  if (!content) return <div>Loading...</div>;

  return <div>{content}</div>;
}

Choosing the Right Strategy

The strategy you choose should align with your app's core needs:

Use Network Only for:

  • Real-time features like notifications or live feeds
  • Critical data that must be fresh (banking, stock prices)
  • Simple apps where cache complexity isn't worth it

Opt for Network + Cache for:

  • Blogs, user profiles, or data that updates periodically
  • E-commerce product listings
  • Social media feeds
  • Most web applications

Go with Cache Only for:

  • Static content, offline apps, or embedded devices
  • Configuration data that rarely changes
  • PWA offline functionality

Advanced Patterns

Service Worker Caching

// Service worker for advanced caching
self.addEventListener('fetch', (event) => {
  if (event.request.url.includes('/api/')) {
    event.respondWith(
      caches.match(event.request)
        .then((response) => {
          // Return cached version if available
          if (response) {
            return response;
          }
          
          // Fetch from network
          return fetch(event.request)
            .then((response) => {
              // Cache the response for future use
              const responseClone = response.clone();
              caches.open('api-cache').then((cache) => {
                cache.put(event.request, responseClone);
              });
              return response;
            });
        })
    );
  }
});

SWR Pattern (Stale-While-Revalidate)

import useSWR from 'swr';

function UserProfile({ userId }) {
  const { data, error, isLoading } = useSWR(
    `/api/users/${userId}`,
    fetcher,
    {
      revalidateOnFocus: false,
      revalidateOnReconnect: true,
      refreshInterval: 30000, // Refresh every 30 seconds
    }
  );

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error loading user</div>;

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.email}</p>
    </div>
  );
}

Performance Comparison

StrategyInitial LoadSubsequent LoadsOffline SupportData Freshness
Network OnlySlowSlow✅ Always Fresh
Network + CacheFastVery Fast⚠️ Potentially Stale
Cache OnlyVery FastVery Fast❌ May Be Stale

Final Thoughts

Track performance with tools like Chrome DevTools, Lighthouse, or React Profiler, and adapt as your app scales. The best approach often combines multiple strategies based on different types of data in your application.

Want to go deeper? Pair this with service workers, SWR/React Query, and IndexedDB strategies to unlock serious frontend power 💪.


Next: Apollo Client Caching - Learn how to master Apollo Client's powerful caching system.