useSWRSubscription
Purpose and Scope
useSWRSubscription is the SWR entrypoint for wiring an SWR resource to an external source that pushes updates over time. Instead of asking a fetcher to request a value once, the hook accepts a subscriber function that connects to a data source, reports each new value through a callback, and returns an unsubscribe function for cleanup. This makes it the right API for sources such as sockets, event emitters, browser APIs, collaborative state channels, or any adapter where the data producer controls when updates arrive.
Sources: src/subscription/index.ts, src/subscription/types.ts
SWR already frames the library as a React Hooks data-fetching system that keeps interfaces fast and reactive by returning cached data, revalidating, and delivering a stream of data updates. The subscription hook fits that broader model by using the same cache and mutation machinery as the main hook while replacing the normal request lifecycle with a continuous subscription lifecycle. Readers who already know useSWR should think of this API as a cache-backed bridge from external event streams into SWR state, not as a separate state container.
Sources: README.md, src/subscription/index.ts
The implementation marks the hook as experimental in its public comment, so application code should treat the exact API as subject to future change. Even with that caveat, the source defines a clear contract today: call the hook with a key, a subscription function, and optional SWR configuration; receive a response with data and error; call next from the external subscription whenever the source emits either a value or a failure; and always return a cleanup function. That cleanup requirement is enforced at runtime, not merely described by TypeScript.
Sources: src/subscription/index.ts, src/subscription/types.ts
Relevant Source Files
src/subscription/types.ts— Defines the public TypeScript contract for subscription options, subscriber functions, hook responses, and theSWRSubscriptionHookcall signature.src/subscription/index.ts— Implements the subscription middleware, the defaultuseSWRSubscriptionexport, key prefixing, cache-scoped reference counting, update propagation, and cleanup validation.README.md— Provides the repository-level description of SWR as a React Hooks data-fetching library with cache, real-time updates, and reactive streams of data.subscription/package.json— Describes the published subpackage metadata for the subscription entrypoint, including CommonJS, ES module, and declaration file targets.e2e/site/README.md— Documents how the repository’s Next.js end-to-end site can be started for browser-oriented validation work.
Public API Reference
The default import is useSWRSubscription from the swr/subscription subpath. Its type is exported as SWRSubscriptionHook, which is generic over the data type, error type, and key type. The call shape is a hook call with three inputs: a key, a subscriber, and optional SWR configuration. The response is intentionally small: it exposes optional data and optional error. Unlike the primary useSWR return object, the subscription response type in the supplied source does not include loading flags, validation flags, or a bound mutate function.
Sources: src/subscription/types.ts, src/subscription/index.ts
The subscriber function receives the resolved key argument and an options object containing next. Calling next with an error records that error in the SWR cache entry. Calling next with no error and a data value clears the previous error and writes the new value by delegating to SWR mutation without triggering revalidation. The data parameter can also be a MutatorCallback, so a subscription can compute its next value from the previous cached value when the event source only sends a patch or increment rather than a complete replacement.
Sources: src/subscription/types.ts, src/subscription/index.ts
import useSWRSubscription from 'swr/subscription'
function LiveWidget({ roomId }) {
const { data, error } = useSWRSubscription(
() => roomId ? ['/rooms', roomId] : null,
(key, { next }) => {
const unsubscribe = dataSource.subscribe(key, (err, value) => {
next(err, value)
})
return unsubscribe
}
)
if (error) return <p>subscription failed</p>
return <pre>{JSON.stringify(data)}</pre>
}The key type follows SWR’s normal key family, but the subscription type applies conditional inference so the subscriber receives the useful argument form. When the key is a function that returns an argument or a disabled value, the subscriber is typed with the returned argument. When the key is directly null, undefined, or false, the subscriber type becomes unusable because there is no active subscription to establish. For ordinary keys, the subscriber receives that key argument directly. This mirrors the runtime behavior where serialization may produce no cache key, in which case the effect exits without subscribing.
Sources: src/subscription/types.ts, src/subscription/index.ts
Runtime Flow and Cache Integration
At runtime, the exported hook is assembled by wrapping the normal SWR hook with a subscription middleware. The middleware serializes the user key into a stable cache key and argument payload, prefixes the cache key with a subscription marker, and calls the next SWR hook with that subscription key and a null fetcher. The prefix is an important implementation detail because it keeps subscription cache entries from colliding with ordinary resources that happen to use the same user-facing key. The null fetcher also communicates that updates are driven by the external subscription rather than by an SWR fetcher.
Sources: src/subscription/index.ts
The hook stores subscription bookkeeping in a WeakMap keyed by the active cache object. Each cache boundary gets its own pair of maps: one map tracks how many mounted hook instances are using each subscription key, and the other stores the disposer function returned by the subscriber. This design matters for applications that use separate SWR provider zones. Two zones can use the same subscription key without sharing an underlying subscription, because the bookkeeping belongs to the cache instance supplied through configuration rather than to a single global registry.
Sources: src/subscription/index.ts
The subscription is established inside an isomorphic layout effect. When the serialized subscription key is missing, the effect returns immediately and no subscriber is called. When a key is present, the middleware creates a cache helper for the prefixed key, reads the current reference count, increments that count, and only calls the user subscriber when the count was previously zero. That means multiple components can read the same subscription-backed resource without opening duplicate connections. They share the same cache entry and the same external subscription while they are mounted inside the same cache boundary.
Sources: src/subscription/index.ts
Cleanup is reference-counted as well. When a component unmounts or the subscription key changes, the effect decrement path reduces the count for the prefixed key. If other components still need the same subscription, the external connection remains open. If the count reaches zero, the stored disposer is called. The implementation also validates the setup path by throwing an error if the subscriber does not return a function. That runtime guard is significant because forgetting to unsubscribe from a push-based data source can cause leaked sockets, listeners, timers, or stale callbacks.
Sources: src/subscription/index.ts
Update Semantics and Error Handling
The next callback is the only supported way for the external source to push state into SWR through this middleware. Its first parameter is treated as an error channel. If the value is neither null nor undefined, the middleware writes an object containing that error to the subscription cache entry. If there is no error, the middleware clears the cached error and calls the underlying SWR mutate method with the supplied data or mutator callback, passing a flag that avoids revalidation. This keeps external events from accidentally causing an additional fetch cycle after every pushed update.
Sources: src/subscription/index.ts, src/subscription/types.ts
This behavior means subscription producers should be deliberate about whether they are sending a failure or a data update. Passing null or undefined as the first argument is the success path, even if the second argument is omitted. Passing an error records the failure and does not also mutate data through the success branch. A practical adapter often forwards an event emitter or socket callback directly, but more complex adapters may normalize protocol messages first, then call next with either a domain-specific error object or the resolved application value.
Sources: src/subscription/index.ts
Because the hook response is implemented with property getters that return swr.data and swr.error, consumers always read the current values from the underlying SWR hook result. The subscription middleware does not invent a separate rendering model; it lets SWR’s cache update and React subscription mechanisms do the work. This is why the API composes naturally with SWR configuration, cache providers, and provider boundaries. A component can consume the returned data and error just as it would for a normal resource, while the producer side remains event driven.
Sources: src/subscription/index.ts
Packaging and Configuration Notes
The subscription entrypoint is distributed as a subpackage rather than only as a named export from the core module. The subpackage metadata points CommonJS consumers at ../dist/subscription/index.js, ES module consumers at ../dist/subscription/index.mjs, and TypeScript users at ../dist/subscription/index.d.ts. The package file is marked private in the repository workspace layout, but the root package export map in the supplied evidence also exposes the ./subscription subpath for import and require builds. For application code, the stable import style is the documented subpath.
Sources: subscription/package.json, src/subscription/index.ts
Configuration is passed through to the underlying SWR hook and is typed as SWR configuration at the public boundary. Inside the middleware, the implementation depends on the resolved config having a cache object, because the cache is used both for SWR data and for subscription bookkeeping. This means ordinary SWR provider decisions still matter. If an application nests cache providers, subscriptions are scoped to the provider zone. If two components are under the same provider and use the same serialized key, the reference-counting path shares the underlying subscription.
Sources: src/subscription/index.ts, src/subscription/types.ts
Development and Validation Signals
For local browser validation, the repository includes a Next.js end-to-end site README that explains the standard development server commands and directs readers to open the local site in a browser. That file is not a subscription-specific recipe, but it is useful context for contributors who need to reproduce interactive behavior in an application shell. A subscription integration is easiest to inspect in a running React environment because the important lifecycle events are mount, shared subscription reuse, key changes, pushed updates, and cleanup after unmount.
Sources: e2e/site/README.md, src/subscription/index.ts
When validating a subscription adapter, test the lifecycle rather than only the first value. Mount two consumers with the same key and confirm that the external source is subscribed once. Unmount one and confirm the connection remains active. Unmount the last consumer and confirm the disposer runs. Then repeat with a disabled key that resolves to null or false and verify that no subscription is opened. Finally, exercise the error path and the success path separately so the adapter does not accidentally retain an old error after a successful update.
Sources: src/subscription/index.ts, src/subscription/types.ts
Related Pages and Next Steps
Use this page when you need the exact contract for continuous external updates. Read the core useSWR reference next if you need to understand the shared cache, response state, and configuration behavior that the middleware delegates to. Read the cache and provider documentation when using multiple SWR zones, because provider boundaries control whether identical subscription keys share a connection. Read the mutation documentation if your subscription source sends patches and you want to use a mutator callback to derive the next cached value from the previous one.
Sources: README.md, src/subscription/index.ts