Stale-While-Revalidate Model

Purpose and Scope

SWR is named after the stale-while-revalidate cache strategy, and the repository README defines that strategy as the central mental model for the library. In SWR, a component can receive cached data first, even when that data may be stale, while the hook starts an asynchronous request to revalidate the value. When the fetcher resolves, SWR updates the cache and rerenders the component with fresh data. This makes the UI responsive without giving up eventual correctness, and it explains why most SWR APIs are organized around keys, fetchers, cache state, and revalidation triggers.

Sources: README.md

This page focuses on the lifecycle that every higher-level feature builds on: read from cache, start or skip a revalidation according to configuration, publish intermediate loading or validating state, then commit the latest accepted result. The README describes this as a stream of data updates that keeps the UI fast and reactive. The integration and revalidation tests show that the model is not only a slogan; it is validated through hydration behavior, fallback data, mount options, deduplication, manual mutation, race handling, and shared-key synchronization.

Sources: README.md, test/use-swr-integration.test.tsx, test/use-swr-revalidate.test.tsx

Relevant Source Files

  • README.md — Defines SWR as a React Hooks data-fetching library, introduces stale-while-revalidate, and shows the minimal useSWR(key, fetcher) lifecycle with data, isLoading, and error.
  • test/use-swr-integration.test.tsx — Exercises the integration behavior that makes the model observable in React components, including hydration, async fetchers, revalidateOnMount, fallback data, disabled revalidation, and request deduplication.
  • test/use-swr-revalidate.test.tsx — Exercises explicit revalidation through mutate, shared-key updates across hooks, concurrent request state, and sequence handling for race conditions.

Lifecycle: Cache First, Then Revalidate

The first phase is a cache read. If SWR already has data for the key, that value can be returned immediately. If no cached value is available, the initial render may show undefined data while the hook prepares the request. The README quick start demonstrates this through the public return values: data is initially unavailable, isLoading is true while the fetcher has not finished, and the component rerenders after the asynchronous result arrives. The integration test for hydration makes the same behavior concrete by first rendering an empty greeting and then rendering the fetched value.

Sources: README.md, test/use-swr-integration.test.tsx

The second phase is revalidation. Revalidation means SWR invokes the fetcher for the current key and uses the result to update the cache. The key identifies the request, and the fetcher is the asynchronous function that produces the data. In the default path, mounting a hook with a key and fetcher leads to a request, then a rerender with the resolved value. Because the fetcher is transport agnostic, this lifecycle applies equally to fetch, GraphQL clients, SDK calls, local async functions, and any other promise-returning data source.

Sources: README.md, test/use-swr-integration.test.tsx

The stale-while-revalidate model also explains why fallback data and loading state are separate concerns. A hook may start with fallbackData, which lets the component render useful content before the fetcher returns. If revalidateOnMount is true, the hook still fetches and eventually replaces that fallback value with the fetched result. If revalidation is disabled through options such as revalidateIfStale: false, revalidateOnFocus: false, and revalidateOnReconnect: false, the tests show that the fallback value can remain stable and the initial loading and validating flags can both be false.

Sources: test/use-swr-integration.test.tsx

Revalidation Controls and Shared Cache Behavior

Revalidation is configurable because not every cached value should be refreshed at the same time. The integration tests show revalidateOnMount: false preventing the initial fetch for a stable key, while a key change can still produce a fetch and update the rendered data. This distinction is important for application code: options can suppress automatic revalidation for one lifecycle moment, but the key remains the identity of the resource. When the key changes, SWR treats it as a different cache entry and the hook can begin a new stale-while-revalidate cycle for that entry.

Sources: test/use-swr-integration.test.tsx

The cache is shared by key, so multiple hooks using the same key participate in the same data stream. The integration tests cover request deduplication by rendering two useSWR calls with the same key and fetcher. The revalidation tests cover the update side: when one hook calls its bound mutate() to revalidate, another hook with the same key receives the refreshed value too. This behavior is essential to SWR's model because the cache is not just an implementation detail; it is the coordination point for rendering consistent data across components.

Sources: test/use-swr-integration.test.tsx, test/use-swr-revalidate.test.tsx

Manual revalidation uses the same conceptual pipeline as automatic revalidation. In the revalidation tests, a component reads data and mutate from useSWR; clicking the button calls mutate(), which reruns the fetcher and rerenders from one value to the next. Another test renders two hooks for the same key and verifies that one revalidation updates both displayed values. For readers, the practical rule is simple: automatic triggers and explicit mutate() calls are different ways to request a fresh value for the same cache identity.

Sources: test/use-swr-revalidate.test.tsx

Correctness Under Concurrency

A cache-first UI is only useful if newer revalidation results do not get overwritten by older requests that finish later. The revalidation tests include sequence and race-condition cases where two revalidations are triggered close together with different response delays. The expected rendered value is the result from the later, faster revalidation, not the stale result from the earlier slow request. This validates an important part of the model: stale data can be displayed while fetching, but SWR must still preserve ordering so the cache converges on the most relevant result.

Sources: test/use-swr-revalidate.test.tsx

SWR also exposes validating state so components can distinguish between having data and actively refreshing data. One test starts with revalidateOnMount: false, triggers one revalidation, triggers another while the first is still in flight, and checks that isValidating remains true until both concurrent requests are complete. This is different from isLoading: a component can already have cached or fallback data and still be validating. That distinction lets user interfaces show subtle refresh indicators instead of replacing useful stale content with a loading screen.

Sources: README.md, test/use-swr-revalidate.test.tsx

How to Apply the Model

When designing a hook with SWR, start by choosing a stable key that represents the resource, then provide a fetcher that can derive the fresh value from that key. Decide whether initial render should use cached data, fallback data, or an empty loading state. Then choose revalidation controls for the user experience: default mount behavior for ordinary remote data, disabled or narrowed revalidation for values that should stay stable, and explicit mutate() when a user action should refresh or update the cache immediately.

Sources: README.md, test/use-swr-integration.test.tsx, test/use-swr-revalidate.test.tsx

The official focus-revalidate example describes an authentication-style flow where revalidation on focus and per-hook revalidation are useful. That fits the same foundation described here: the UI can show the last known session state quickly, then SWR can revalidate when the browser regains focus or when code explicitly asks for a refresh. After this page, read the pages on keys and serialization, cache providers, revalidation strategies, and mutation concepts to see how the stale-while-revalidate lifecycle is specialized for concrete application patterns.

Sources: README.md, test/use-swr-revalidate.test.tsx