Focus Revalidation
Purpose and Scope
This page explains the Focus Revalidate example as a practical way to understand SWR's automatic revalidation model. In SWR, revalidation means running the fetcher again for an existing cache key so components can move from cached or stale data to fresh data. The example is described by the official example README as a basic authentication scenario that shows how focus-driven revalidation works and how revalidation can also be triggered on a per-hook basis. That framing is important: the feature is not only a browser event listener, but part of SWR's broader stale-while-revalidate contract.
Sources: README.md, test/use-swr-revalidate.test.tsx
The README introduces SWR as a React Hooks library for data fetching and explicitly lists revalidation on focus as one of the built-in capabilities. It also defines the stale-while-revalidate lifecycle: return cached data first, send a request to revalidate, and then provide up-to-date data. The focus example should be read through that model. When a user leaves and returns to a tab, SWR can treat the focus event as a signal that visible data may now be stale, then ask registered hooks to refresh themselves.
Sources: README.md, src/_internal/utils/cache.ts
Relevant Source Files
README.md— Defines SWR, the stale-while-revalidate model, the basicuseSWRhook shape, and built-in focus revalidation as a first-class feature.src/_internal/utils/cache.ts— Initializes provider-scoped SWR global state, registers focus and reconnect listeners, and broadcasts focus events to cached-key revalidators.test/use-swr-revalidate.test.tsx— Verifies manual revalidation through boundmutate, shared-key revalidation, sequencing, race handling, concurrent validation state, and deduping behavior.test/use-swr-cache.test.tsx— Shows cached values being read, updated, scoped through providers, and refreshed after cache-backed rendering.e2e/site/app/render-suspense-no-revalidate/page.tsx— Demonstrates a render path where cached data is preloaded andrevalidateIfStale: falseprevents automatic stale revalidation under Suspense.e2e/site/README.md— Documents how to run the local Next.js E2E site used for browser-facing examples and verification.
How the Example Fits SWR
The Focus Revalidate example is best understood as an authentication-style flow because authentication state is often cached, user-visible, and sensitive to time. A tab might remain open while a session expires, while another tab signs out, or while a server-side account state changes. Focus revalidation lets the app cheaply show the last known state while the tab is inactive, then refresh when the user returns. Instead of forcing every component to poll aggressively, SWR can wait for an interaction signal that strongly suggests the user is about to look at the data again.
Sources: README.md, src/_internal/utils/cache.ts
A hook participates in this behavior through its cache key. The README describes the key as the unique identifier of a request, commonly a URL, and the fetcher as the asynchronous function that receives that key. When focus occurs, SWR does not need to know what the request means semantically; it needs to know which hooks have registered revalidators for which serialized keys. That design keeps focus behavior transport-agnostic. An authentication hook, a user profile hook, or a project-permission hook can all be refreshed by the same event pipeline as long as they are represented by SWR keys.
Sources: README.md, src/_internal/utils/cache.ts
Execution Flow
Internally, initCache creates global state for a specific cache provider. That state includes an EVENT_REVALIDATORS registry, a bound mutate function for that provider, a setter that updates provider entries, and a subscription mechanism for cache updates. When running in the browser, provider initialization calls opts.initFocus and opts.initReconnect. The focus handler eventually invokes revalidateAllKeys with the focus event type, and revalidateAllKeys walks the registered keys and calls the first revalidator for each key. This is the source-level bridge between the browser focus signal and hook-level refresh work.
Sources: src/_internal/utils/cache.ts
The implementation deliberately schedules the focus and reconnect broadcasts through setTimeout. The comment in the cache utility explains that the delay ensures native events fire after immediate JavaScript execution, including React state updates, and avoids unnecessary revalidations. For application developers, this means focus revalidation should be treated as automatic but not as a synchronous callback. The hook state will update after SWR runs its revalidation work, deduplicates or sequences requests as needed, and commits the latest accepted result into the provider cache.
Sources: src/_internal/utils/cache.ts, test/use-swr-revalidate.test.tsx
Per-Hook Revalidation Controls
The example README calls out triggering revalidation on a per-hook call basis. In normal hook code, the most direct per-hook trigger is the bound mutate returned by useSWR. The revalidation tests include a component that reads { data, mutate } from useSWR and revalidates when a button is clicked. After the click, the test waits for the next tick and observes the updated value. This is the manual counterpart to focus revalidation: both paths converge on rerunning the fetcher for the key, but one is caused by user code and the other by SWR's browser event integration.
Sources: test/use-swr-revalidate.test.tsx
Per-hook control also matters when multiple hook instances share a key. The revalidation tests mount two useSWR calls with the same key and show that a single bound mutate updates both rendered values. That behavior is critical for auth-style examples, where a header, sidebar, and route guard may all depend on the same current-user key. Focus revalidation should not create isolated refreshes that leave sibling components inconsistent. SWR's cache-key model lets the refresh propagate to every subscriber using that key within the same provider scope.
Sources: test/use-swr-revalidate.test.tsx, test/use-swr-cache.test.tsx
Cache, Provider, and Stale Data Behavior
Focus revalidation is cache-aware. The cache tests show SWR reading an initial provider value, rendering cached data, then updating to a fetched value. That is the same user-facing pattern described in the README's stale-while-revalidate explanation: stale data can be useful immediately, and a background request can replace it with fresh data later. In the focus example, that means an authenticated UI can remain responsive with cached session data while SWR confirms whether the server still agrees. The feature is designed for responsiveness without giving up correctness over time.
Sources: README.md, test/use-swr-cache.test.tsx
Provider scope controls how broadly focus-driven updates apply. The cache tests demonstrate nested and isolated providers with the same key returning different values, because each provider owns its cache state. Since initCache registers event revalidators for a specific provider, focus revalidation is naturally scoped to that provider's global state. If an application uses multiple SWRConfig providers with custom provider functions, the same key string can represent separate cached records. That is powerful for tests, embedded widgets, or multi-tenant UI boundaries, but it also means developers should choose provider boundaries intentionally.
Sources: src/_internal/utils/cache.ts, test/use-swr-cache.test.tsx
Edge Cases and Tuning Signals
Not every stale render should automatically revalidate. The E2E Suspense page preloads a key with the value cached, then calls useSWR with suspense: true and revalidateIfStale: false. The fetcher would eventually return fresh, but the configuration tells SWR not to revalidate merely because cached data exists and might be stale. This is useful context for focus examples: focus revalidation is a default convenience, while options such as revalidateIfStale let a hook opt into a calmer render path when preloaded or fallback data should be trusted for that moment.
Sources: e2e/site/app/render-suspense-no-revalidate/page.tsx
The revalidation tests also highlight race and concurrency behavior that affects real focus flows. One test triggers a slower revalidation and then a faster one, expecting the newer result to win. Another keeps isValidating true while overlapping requests are still in flight. A third verifies sequencing even inside a deduping interval. These behaviors matter when a focus event, a manual button click, and a retry or interval refresh happen close together. The practical rule is to write fetchers as idempotent data reads and let SWR arbitrate ordering and validation state for the shared key.
Sources: test/use-swr-revalidate.test.tsx
Running the Example Locally
The official Focus Revalidate example can be downloaded from the repository's examples directory and run as a standalone app. The documented flow is to fetch the example archive, enter the focus-revalidate directory, install dependencies, and start the development server. The E2E site README shows the same local development pattern for the repository's Next.js site: run a development command, open the local server in a browser, and edit app files while the page auto-updates. Use the browser itself as part of the test: load the page, switch tabs or windows, then return and observe the hook refresh behavior.
Sources: e2e/site/README.md
curl https://codeload.github.com/vercel/swr/tar.gz/main | tar -xz --strip=2 swr-main/examples/focus-revalidate
cd focus-revalidate
yarn
yarn dev
# or
npm install
npm run devCompact Reference
| Concern | What to use | Source-backed behavior |
|---|---|---|
| Automatic focus refresh | Default SWR focus revalidation | Provider initialization registers focus listeners that broadcast a focus event to key revalidators. |
| Manual per-hook refresh | Bound mutate from useSWR | Calling the bound mutate in tests reruns the fetcher and updates the rendered data. |
| Shared auth state | Reuse the same SWR key | Hooks with the same key receive the same revalidated result in the revalidation tests. |
| Provider isolation | SWRConfig with provider | Cache tests show the same key can resolve to different values in isolated providers. |
| Avoid stale revalidation | revalidateIfStale: false | The Suspense E2E page preloads cached data and prevents stale-triggered revalidation. |
Next Steps
After studying the focus example, read the revalidation strategy and cache-provider material next. Focus revalidation is easiest to reason about once you understand three primitives together: keys identify cache records, providers scope those records and their event listeners, and mutate/revalidation APIs refresh them. For authentication flows, keep the current-user key stable, use per-hook options only where a screen needs different freshness semantics, and prefer shared cache state over duplicating request logic across components.