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
dedupingIntervalvalues - 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:
dedupingIntervalis configured below the average API latency, forcing SWR to treat rapid successive calls as distinct.- Concurrent
useSWRinstances 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:
- Render a grid component with 20+ items, each invoking
useSWR('/api/metrics'). - Trigger a rapid parent state update (e.g., toggle filter).
- Observe the Network tab: identical URLs fire in parallel instead of coalescing.
DevTools/Profiler Workflow:
- Open Chrome DevTools > Network > Filter by
Fetch/XHR> Enable Disable Cache. - Sort by
Timeand look for identical endpoints with<50msdeltas. - Switch to React DevTools > Profiler > Record interaction. Identify components with excessive re-renders initiating duplicate SWR hooks.
- Inspect the
Initiatorcolumn in Network tab to trace duplicate calls to specific component mounts.
Resolution & Configuration:
- Increase
dedupingIntervalto2000–5000msto 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:
- Configure
refreshInterval: 1000on a high-latency endpoint. - Throttle network to
Slow 3Gin DevTools. - Observe queued requests in the Network tab and monitor main-thread blocking in the Performance panel.
DevTools/Profiler Workflow:
- Open DevTools > Performance > Record > Filter to
NetworkandMain. - Look for long tasks (>50ms) triggered by JSON parsing during polling cycles.
- Run
navigator.connection.effectiveTypein the Console to verify baseline network assumptions. - Use the Application tab to inspect SWR cache state under
Local StorageorIndexedDB(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:
revalidateOnReconnectorrevalidateOnFocusenabled 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:
- Disable network in DevTools > Network tab.
- Wait 5 seconds, then re-enable network.
- Rapidly switch browser tabs 3–4 times.
- Monitor request queue: observe burst of identical
GETrequests and potential429 Too Many Requestsresponses.
DevTools/Profiler Workflow:
- Use Application > Service Workers to simulate offline/online transitions.
- Enable Preserve log in Network tab to track request bursts across visibility changes.
- In React DevTools, inspect component state during tab switches to verify if
mutate()is queued or dropped. - 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.