Optimizing SWR Revalidation Intervals

A production-grade guide to configuring SWR’s revalidation triggers (refreshInterval, revalidateOnFocus, revalidateOnReconnect, dedupingInterval) to eliminate network thrashing while maintaining UI consistency. This resource maps real-world symptoms to root causes, providing step-by-step resolutions for edge cases in high-concurrency environments. For broader architectural context, explore Background Refetch Strategies and align your polling logic with established Cache Invalidation & Server Synchronization patterns.

Key Diagnostic Objectives:

  • Identify when fixed polling intervals cause API rate-limit exhaustion
  • Map duplicate request waterfalls to misconfigured dedupingInterval values
  • Implement adaptive revalidation based on connection quality and data volatility
  • Resolve race conditions between background polling and optimistic mutations
  • Leverage targeted cache keys to prevent global state invalidation cascades

Diagnosing Network Thrashing & Duplicate Requests

Observable Symptoms: Multiple identical GET requests fire within milliseconds of each other, causing API rate-limit headers to trigger, increased Time-to-First-Byte (TTFB), and UI jitter during list renders.

Root-Cause Analysis:

  • dedupingInterval is configured below the average API latency, forcing SWR to treat rapid successive calls as distinct.
  • Concurrent useSWR instances share identical cache keys without strict normalization, bypassing the internal deduping queue.
  • Parent component re-renders trigger uncontrolled hook instantiation in virtualized lists.

Reproduction Steps:

  1. Render a grid component with 20+ items, each invoking useSWR('/api/metrics').
  2. Trigger a rapid parent state update (e.g., toggle filter).
  3. Observe the Network tab: identical URLs fire in parallel instead of coalescing.

DevTools/Profiler Workflow:

  1. Open Chrome DevTools > Network > Filter by Fetch/XHR > Enable Disable Cache.
  2. Sort by Time and look for identical endpoints with <50ms deltas.
  3. Switch to React DevTools > Profiler > Record interaction. Identify components with excessive re-renders initiating duplicate SWR hooks.
  4. Inspect the Initiator column in Network tab to trace duplicate calls to specific component mounts.

Resolution & Configuration:

  • Increase dedupingInterval to 2000–5000ms to align with typical API response windows.
  • Enforce strict key normalization: useSWR(['metrics', userId, filter], fetcher).
  • Memoize hook dependencies and audit list render cycles to prevent unnecessary hook recreation.

Trade-offs:

  • Higher deduping windows reduce network load but may delay UI updates during rapid user interactions.
  • Strict key normalization prevents duplicates but increases initial setup complexity and requires disciplined cache key hygiene.

Tuning refreshInterval for Dynamic Data

Observable Symptoms: Dashboard metrics feel stale despite active polling, or mobile battery drain spikes during background tab sessions.

Root-Cause Analysis: Static refreshInterval values ignore actual data change frequency, user connection quality, and server capacity, leading to wasted bandwidth on low-churn endpoints and throttled responses on constrained networks.

Reproduction Steps:

  1. Configure refreshInterval: 1000 on a high-latency endpoint.
  2. Throttle network to Slow 3G in DevTools.
  3. Observe queued requests in the Network tab and monitor main-thread blocking in the Performance panel.

DevTools/Profiler Workflow:

  1. Open DevTools > Performance > Record > Filter to Network and Main.
  2. Look for long tasks (>50ms) triggered by JSON parsing during polling cycles.
  3. Run navigator.connection.effectiveType in the Console to verify baseline network assumptions.
  4. Use the Application tab to inspect SWR cache state under Local Storage or IndexedDB (if persisted) to verify stale-while-revalidate behavior.

Resolution & Configuration: Implement a dynamic refreshInterval function that evaluates connection state, server Retry-After headers, or custom telemetry. This aligns polling cadence with actual data volatility and client capabilities.

import useSWR from 'swr';

const fetcher = (url) => fetch(url).then((res) => res.json());

export function useAdaptiveData(endpoint: string) {
  const getInterval = () => {
    const conn = navigator.connection as NetworkInformation | undefined;
    if (conn?.effectiveType === '4g') return 5000;
    if (conn?.effectiveType === '3g') return 15000;
    return 30000; // Fallback for slow/poor connections
  };

  return useSWR(endpoint, fetcher, {
    refreshInterval: getInterval,
    dedupingInterval: 3000,
    revalidateOnFocus: false,
    keepPreviousData: true,
  });
}

Cache Behavior Explanation: SWR evaluates the refreshInterval function on each polling cycle. It merges cached responses with fresh data without blocking the UI thread, and respects dedupingInterval to coalesce rapid successive calls into a single network request.

Trade-offs:

  • Adaptive intervals improve efficiency but require additional client-side logic and cross-browser connection API testing.
  • Fixed intervals are easier to debug but waste bandwidth on low-churn endpoints and degrade performance on constrained networks.

Managing Focus & Reconnect Revalidation

Observable Symptoms: Stale data persists after network drop recovery, or sudden tab focus triggers 50+ concurrent requests, overwhelming the server and causing UI layout shifts.

Root-Cause Analysis:

  • revalidateOnReconnect or revalidateOnFocus enabled globally without debouncing.
  • Race conditions occur when visibility events fire alongside pending mutate() calls, causing optimistic UI updates to be overwritten.
  • Lack of per-hook scoping forces unrelated components to refetch simultaneously.

Reproduction Steps:

  1. Disable network in DevTools > Network tab.
  2. Wait 5 seconds, then re-enable network.
  3. Rapidly switch browser tabs 3–4 times.
  4. Monitor request queue: observe burst of identical GET requests and potential 429 Too Many Requests responses.

DevTools/Profiler Workflow:

  1. Use Application > Service Workers to simulate offline/online transitions.
  2. Enable Preserve log in Network tab to track request bursts across visibility changes.
  3. In React DevTools, inspect component state during tab switches to verify if mutate() is queued or dropped.
  4. Use window.performance.getEntriesByType('resource') to measure actual fetch latency vs. SWR’s internal timeout thresholds.

Resolution & Configuration: Scope revalidation flags per-hook, implement focus event debouncing, and pause background polling during active mutations. Integrate with mutation rollback patterns to ensure state consistency across network transitions.

import useSWR, { mutate } from 'swr';
import { useState, useRef, useEffect } from 'react';

export function useSafeRefetch(key: string, fetcher: (key: string) => Promise<any>) {
  const isMutating = useRef(false);
  const { data, error } = useSWR(key, fetcher, {
    revalidateOnFocus: true,
    revalidateOnReconnect: true,
    refreshInterval: 0,
  });

  useEffect(() => {
    const handleVisibility = () => {
      if (!document.hidden && !isMutating.current) {
        mutate(key);
      }
    };
    document.addEventListener('visibilitychange', handleVisibility);
    return () => document.removeEventListener('visibilitychange', handleVisibility);
  }, [key]);

  return { data, error, isMutating };
}

Cache Behavior Explanation: SWR’s internal state machine queues manual mutate() calls triggered by visibility changes, bypasses duplicate fetches via dedupingInterval, and safely skips revalidation when an optimistic mutation is in flight.

Trade-offs:

  • Aggressive reconnect refetching guarantees freshness but risks server overload during ISP outages.
  • Disabling focus/reconnect revalidation saves bandwidth but requires explicit user actions or WebSocket events to refresh data.

Common Pitfalls & Prevention Tactics

Issue Root Cause Resolution
Race conditions between mutations and background polling refreshInterval fires while a mutate() is pending, overwriting optimistic UI updates with stale server data. Pause polling during active mutations by conditionally returning 0 from refreshInterval or using SWRConfig to scope revalidation flags per-route.
Memory leaks from unmounted polling hooks Custom setInterval wrappers bypass SWR’s internal cleanup, causing background requests to fire after component unmount. Rely exclusively on SWR’s native refreshInterval prop, which automatically halts timers on unmount, and audit third-party polling utilities.
Excessive cache invalidation causing waterfall refetches Broad cache key patterns (e.g., /api/*) trigger global revalidation instead of targeted updates. Use precise, deterministic cache keys and leverage mutate(key, data, { revalidate: false }) for surgical cache updates without triggering network calls.

Frequently Asked Questions

How do I prevent SWR from refetching on every tab focus?

Set revalidateOnFocus: false globally in SWRConfig or per-hook, and trigger manual revalidation only on explicit user actions or WebSocket events.

What’s the optimal dedupingInterval for high-traffic dashboards?

Start with 2000–5000ms; align it with your API’s average response time and expected data change frequency to prevent duplicate requests during rapid UI interactions.

Can I dynamically adjust refreshInterval based on server load?

Yes, by returning a function from refreshInterval that evaluates connection state, server Retry-After headers, or custom telemetry metrics.