Stale-While-Revalidate Caching in JavaScript
The stale-while-revalidate (SWR) caching strategy
optimizes web performance and user experience by serving cached (stale)
content immediately while asynchronously fetching updated (fresh) data
in the background. This article explains how the pattern works, its
primary benefits, and how to implement it in JavaScript using native
Service Workers and client-side data-fetching libraries.
What is the Stale-While-Revalidate Pattern?
Stale-while-revalidate is a cache invalidation strategy that eliminates latency for the end user. When a client requests a resource:
- Stale Response: The application immediately returns the cached version of the data, regardless of whether it might be slightly outdated.
- Revalidation: Simultaneously, a network request is dispatched in the background to fetch the latest version of the data from the server.
- Cache Update: Once the server responds, the cache is updated with the fresh data, and the UI can optionally update to reflect the changes.
This approach balances two competing goals: ultra-fast load times (zero network wait time on subsequent visits) and eventual data consistency.
HTTP Header Implementation
Before exploring JavaScript implementations, it is worth noting that
stale-while-revalidate is natively supported by modern browsers and CDNs
via the standard HTTP Cache-Control header:
Cache-Control: max-age=60, stale-while-revalidate=300
max-age=60: The response is considered fresh for 60 seconds.stale-while-revalidate=300: If a request occurs between 60 and 360 seconds, the browser serves the stale cache instantly and revalidates in the background. After 360 seconds, the cache is fully expired, and the browser must wait for a network response.
Implementing in JavaScript with Service Workers
In client-side JavaScript, you can implement this pattern manually using the Cache API inside a Service Worker. This is ideal for static assets or offline-first progressive web apps (PWAs).
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('dynamic-cache-v1').then(async (cache) => {
// 1. Check for cached version
const cachedResponse = await cache.match(event.request);
// 2. Fetch fresh data in the background to update the cache
const fetchPromise = fetch(event.request).then((networkResponse) => {
if (networkResponse.status === 200) {
cache.put(event.request, networkResponse.clone());
}
return networkResponse;
});
// 3. Return cached response immediately, or fallback to the network
return cachedResponse || fetchPromise;
})
);
});Implementing in JavaScript with Client-Side Libraries
For dynamic API requests and state management, JavaScript frameworks commonly rely on dedicated libraries designed around this pattern.
1. Vercel’s SWR Library
(React)
The swr package popularized this pattern for
component-level data fetching:
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then((res) => res.json());
function UserProfile({ userId }) {
// Returns cached data instantly, then triggers revalidation
const { data, error, isValidating } = useSWR(`/api/user/${userId}`, fetcher);
if (error) return <div>Failed to load</div>;
if (!data) return <div>Loading...</div>;
return (
<div>
<h1>{data.name}</h1>
{isValidating && <span>Updating in background...</span>}
</div>
);
}2. TanStack Query (React Query)
TanStack Query implements the stale-while-revalidate model by default
through its staleTime and gcTime
configurations:
import { useQuery } from '@tanstack/react-query';
function ProductList() {
const { data, isFetching } = useQuery({
queryKey: ['products'],
queryFn: () => fetch('/api/products').then((res) => res.json()),
staleTime: 1000 * 60, // Data remains fresh for 1 minute
});
return (
<div>
<ul>{data?.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
{isFetching && <p>Background refresh in progress...</p>}
</div>
);
}When to Use Stale-While-Revalidate
The pattern is ideal for: - User profile data, avatars, and configuration settings. - Social media feeds and public comments. - Product catalogs and search results.
It is not recommended for: - Real-time financial or stock transaction data. - Payment checkouts or inventory reservation steps where strict, immediate consistency is required.