Migrate from v1
This page covers only the breaking changes that require code changes when moving from swrv@1.x to SWRV 2.
If your app only uses basic useSWRV(key, fetcher, config) reads and does not rely on the patterns below, the dependency switch should be small. Those reads still work, and you do not need a <SWRVConfig> wrapper just to keep existing read calls running.
Start here
Read the guide as three questions:
- Do you use
mutate? - Do you rely on removed cache or config APIs?
- Does your app render on the server?
If the answer is "no" to all three, you likely do not need any code migration beyond the version switch.
1. If you use mutate, migrate by intent
This is the most important migration area.
In swrv@1, mutate was used for several different jobs:
- start a request early and let a later
useSWRVcall consume it - ask SWRV to fetch again for an existing key
- replace or update cached data
Those jobs sound similar at the call site, but they are different operations:
- prefetch means "start a request now so the data is ready earlier"
- revalidate means "run the existing fetch logic again for this key"
- cache write means "change what is currently stored for this key"
SWRV 2 makes those semantics more explicit. The main rule is:
- use
preload(...)for prefetch - use
mutate(key)or boundmutate()for revalidation - use
mutate(key, data, options)or boundmutate(data, options)for cache writes
Decision table
| What you mean | v1 shape | SWRV 2 shape |
|---|---|---|
| Start a request before a component uses the key | mutate(key, promise) | preload(key, fetcher) |
| Fetch the current key again | bound mutate() | bound mutate() |
| Fetch a known key again after some external action | mutate(key, fetchPromise) or other revalidation uses | mutate(key) |
| Replace or transform cached data | mixed into mutate(...) usage | mutate(data, options) or mutate(key, data, options) |
A. If you were using mutate(key, promise) to prefetch
That pattern must become preload(key, fetcher).
Before:
import { mutate } from "swrv";
function prefetchUser() {
mutate(
"/api/user",
fetch("/api/user").then((response) => response.json()),
);
}After:
import { preload } from "swrv";
const fetcher = (url: string) => fetch(url).then((response) => response.json());
function prefetchUser() {
preload("/api/user", fetcher);
}Why this changed:
- a promise represents one specific in-flight request
- a fetcher represents "how to fetch this key whenever SWRV needs it"
- SWRV 2 moves early request-starting into
preload, because that is semantically different from mutating cached data
Migration steps:
- Keep the same key.
- Extract the request logic into a fetcher function if it is not already one.
- Replace
mutate(key, promise)withpreload(key, fetcher).
B. If you were using mutate() to refetch the current key
That intent stays the same.
const { mutate } = useSWRV("/api/user", fetcher);
await mutate();Why this still works:
- bound
mutate()means "revalidate the key already bound to thisuseSWRVcall" - there is no ambiguity because no new data was passed
C. If you were using mutate(key, fetchUser()) after a write
That usually means "the remote write finished; now fetch fresh data for this key again."
Before:
await updateUser(input);
await mutate("/api/user", fetchUser());After:
import { mutate } from "swrv";
await updateUser(input);
await mutate("/api/user");Why this changed:
- revalidation should describe which key to fetch again, not pass a second ad hoc fetch promise
mutate(key)now clearly means "run the existing fetch logic for this key again"
Migration steps:
- Keep the remote write exactly as it is.
- Remove the explicit fetch promise from
mutate. - Revalidate by key with
mutate(key).
D. If you were using mutate(...) to change cached data
That intent is still valid, but in SWRV 2 it should be expressed as an explicit cache write.
After:
await mutate((current) => (current ? { ...current, name: "Ada" } : current), {
revalidate: false,
});Why this matters:
mutate()with no data means revalidatemutate(data, options)means write data into the cache- those are different operations, and SWRV 2 expects the call shape to reflect that difference
Migration steps:
- Decide whether the old call meant "fetch again" or "change cached data".
- If it meant "fetch again", use
mutate()ormutate(key). - If it meant "change cached data", pass the next value or updater function, plus options.
2. If you rely on removed cache or config APIs
The second migration bucket is old config and cache surface that no longer exists in SWRV 2.
Quick scan
Search your codebase for:
ttlserverTTLrevalidateDebounceisDocumentVisibleSWRVCacheswrv/dist/cache
A. ttl and serverTTL are gone
Before:
useSWRV("/api/user", fetcher, {
ttl: 60_000,
serverTTL: 1_000,
});There is no direct rename for either option.
The right replacement depends on what the option was actually doing for you:
- if you were using it as a refetch policy, replace that with explicit invalidation through
mutate - if you were using it as storage expiry or persistence, move that behavior into a custom cache provider
- if you were using
serverTTLas a rough SSR handoff mechanism, move that behavior tofallbackor snapshot hydration
The first-principles view is:
ttlandserverTTLare cache-lifetime controls- SWRV 2 does not model cache lifetime as a per-call option anymore
- lifetime and persistence now belong at the provider or SSR handoff layer instead
Migration steps:
- Delete
ttlandserverTTLfrom theuseSWRVconfig. - Decide whether the original reason was refetch policy, storage expiry, or SSR handoff.
- Move that reason to the right replacement layer instead of trying to rename the option.
B. revalidateDebounce is gone
Before:
useSWRV("/api/search", fetcher, {
revalidateDebounce: 200,
});There is no direct replacement.
The first-principles view is:
- SWRV should decide when to revalidate
- your component or route state should decide when the key changes
- debounce belongs on the thing that drives the key, not inside SWRV config
Migration steps:
- Delete
revalidateDebouncefrom theuseSWRVconfig. - Move the debounce to the input, watcher, computed source, or route state that changes the key.
C. isDocumentVisible was renamed
Before:
useSWRV("/api/user", fetcher, {
isDocumentVisible: () => document.visibilityState === "visible",
});After:
useSWRV("/api/user", {
isVisible: () => document.visibilityState === "visible",
});Migration steps:
- Rename
isDocumentVisibletoisVisible. - Keep the function body unless you want different visibility logic.
D. SWRVCache and swrv/dist/cache/* imports are gone
If you imported:
SWRVCacheswrv/dist/cache/adapters/localStorage- other
swrv/dist/cache/*paths
move that logic to the provider model.
Example replacement for persistent storage:
function localStorageProvider() {
const map = new Map<string, unknown>(JSON.parse(localStorage.getItem("app-cache") ?? "[]"));
window.addEventListener("beforeunload", () => {
localStorage.setItem("app-cache", JSON.stringify(Array.from(map.entries())));
});
return map;
}Then provide it:
<SWRVConfig :value="{ provider: localStorageProvider }">
<App />
</SWRVConfig>The first-principles view is:
- old
dist/cache/*imports exposed one concrete cache implementation path - SWRV 2 wants cache ownership to be explicit at the provider boundary
- persistence is now something you build into the provider instead of importing from an internal cache path
Migration steps:
- Remove the old cache import.
- Recreate the needed storage behavior as a provider function.
- Attach that provider through
SWRVConfig.
See Cache for the provider API.
3. If your app renders on the server
If your app is client-only, skip this section.
If it renders on the server, SWRV 2 no longer starts useSWRV fetches during SSR. The server must fetch the data first and hand it to the client explicitly.
That is the main semantic shift:
- in v1, you may have relied on SWRV usage during server render
- in SWRV 2, SSR data ownership is explicit
A. If you only need a few known keys
Use config-level fallback.
const article = await getArticle();
const value = {
fallback: {
"/api/article": article,
},
};Migration steps:
- Fetch the data on the server yourself.
- Put it in
fallbackunder the same SWRV key. - Render the app under that config.
B. If the key is not a simple string
Serialize it first:
import { unstable_serialize } from "swrv";
const key = ["/api/projects", { page: 1 }] as const;
const value = {
fallback: {
[unstable_serialize(key)]: await fetchProjects(),
},
};Migration steps:
- Keep the original public key shape.
- Serialize that key for the
fallbackmap. - Store the server-fetched result under the serialized key.
C. If you need to hand off more than a few keys
Use snapshot helpers instead:
Migration steps:
- Seed a request-scoped SWRV client on the server.
- Serialize its snapshot.
- Hydrate a client from that snapshot on the client.
Final check
Before you call the version switch done, confirm all of the following:
- there are no imports from
swrv/dist/cache/* - there are no remaining uses of
ttl,serverTTL, orrevalidateDebounce - prefetch code uses
preload, notmutate(key, promise) - every
mutatecall was reviewed according to whether it means prefetch, revalidate, or cache write - if the app uses SSR, those paths seed
fallbackor snapshot data explicitly
After that, use the rest of the docs as reference material:
