Skip to main content
Technology & EngineeringCaching Services207 lines

Swr

Implement SWR (stale-while-revalidate) for lightweight client-side data fetching

Quick Summary25 lines
You are an SWR specialist who implements stale-while-revalidate data fetching in React applications. You configure caching, revalidation intervals, mutations, and deduplication using Vercel's SWR library for lightweight, fast client-side data management.

## Key Points

- **Using SWR for client-only state**: SWR is for server state; use useState/Zustand for local state
- **Disabling all revalidation**: Defeats the purpose; keep at least one revalidation trigger
- **String concatenation for complex keys**: Use arrays like `["/api/products", { page, filter }]`
- **Ignoring the error state**: Always handle errors; SWR retries but errors can persist
- React applications needing simple, lightweight data fetching with caching
- Real-time dashboards that benefit from focus-based revalidation
- Projects already on the Vercel/Next.js ecosystem wanting consistency
- Small to medium apps where React Query's API surface is overkill
- Pages needing polling, conditional fetching, or dependent requests

## Quick Example

```bash
npm install swr
```

```env
# No environment variables needed
```
skilldb get caching-services-skills/SwrFull skill: 207 lines
Paste into your CLAUDE.md or agent config

SWR Data Fetching and Caching

You are an SWR specialist who implements stale-while-revalidate data fetching in React applications. You configure caching, revalidation intervals, mutations, and deduplication using Vercel's SWR library for lightweight, fast client-side data management.

Core Philosophy

Stale-While-Revalidate Is the HTTP Pattern

SWR returns cached data first (stale), then fetches fresh data (revalidate), then updates the UI. Users see content instantly while fresh data loads in the background. This pattern, borrowed from HTTP cache semantics, prioritizes perceived performance over strict freshness.

Convention Over Configuration

SWR uses sensible defaults: revalidate on focus, retry on error, deduplicate parallel requests. A basic useSWR(key, fetcher) call handles 80% of use cases. Only configure options when the defaults do not match your needs.

Keys Are Both Identity and Arguments

The SWR key serves as the cache identifier and is passed to the fetcher function. Use string keys for simple cases and arrays/functions for parameterized queries. A null or falsy key disables the request, which is how you implement conditional fetching.

Setup

Install

npm install swr

Environment Variables

# No environment variables needed

Key Patterns

1. Global Configuration with SWRConfig

Do:

import { SWRConfig } from "swr";

const fetcher = async (url: string) => {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json();
};

function App({ children }: { children: React.ReactNode }) {
  return (
    <SWRConfig
      value={{
        fetcher,
        revalidateOnFocus: true,
        dedupingInterval: 2000,
        errorRetryCount: 3,
      }}
    >
      {children}
    </SWRConfig>
  );
}

Not this:

// Defining fetcher inline in every useSWR call
const { data } = useSWR("/api/users", (url) => fetch(url).then((r) => r.json()));
const { data: posts } = useSWR("/api/posts", (url) => fetch(url).then((r) => r.json()));

2. Typed Data Fetching

Do:

import useSWR from "swr";

interface User {
  id: string;
  name: string;
  email: string;
}

function useUser(id: string | null) {
  const { data, error, isLoading, mutate } = useSWR<User>(
    id ? `/api/users/${id}` : null,
    { revalidateOnFocus: false }
  );

  return {
    user: data,
    isLoading,
    isError: !!error,
    mutate,
  };
}

Not this:

// No type safety, no conditional fetching
function useUser(id: string) {
  const { data } = useSWR(`/api/users/${id}`);
  return { user: data as any };
}

3. Mutation with Optimistic Update

Do:

import useSWRMutation from "swr/mutation";
import { mutate } from "swr";

async function updateUser(url: string, { arg }: { arg: Partial<User> }) {
  const res = await fetch(url, {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(arg),
  });
  return res.json();
}

function useUpdateUser(id: string) {
  return useSWRMutation(`/api/users/${id}`, updateUser, {
    optimisticData: (current: User, arg: Partial<User>) => ({ ...current, ...arg }),
    rollbackOnError: true,
    revalidate: true,
  });
}

Not this:

// Manual state management alongside SWR
const [updating, setUpdating] = useState(false);
const handleUpdate = async () => {
  setUpdating(true);
  await fetch(`/api/users/${id}`, { method: "PATCH", body: JSON.stringify(data) });
  setUpdating(false);
  mutate(`/api/users/${id}`);
};

Common Patterns

Polling with refreshInterval

function useLivePrice(symbol: string) {
  return useSWR<{ price: number }>(`/api/stocks/${symbol}`, {
    refreshInterval: 5000,
    revalidateOnFocus: true,
  });
}

Pagination

function useProducts(page: number, limit: number) {
  return useSWR<{ data: Product[]; total: number }>(
    `/api/products?page=${page}&limit=${limit}`,
    { keepPreviousData: true }
  );
}

Prefetching

import { preload } from "swr";

function prefetchUser(id: string) {
  preload(`/api/users/${id}`, fetcher);
}

function UserLink({ id, name }: { id: string; name: string }) {
  return (
    <Link href={`/users/${id}`} onMouseEnter={() => prefetchUser(id)}>
      {name}
    </Link>
  );
}

Global Mutation for Cross-Component Updates

import { useSWRConfig } from "swr";

function LogoutButton() {
  const { mutate } = useSWRConfig();

  const handleLogout = async () => {
    await fetch("/api/auth/logout", { method: "POST" });
    mutate(() => true, undefined, { revalidate: false });
  };

  return <button onClick={handleLogout}>Log out</button>;
}

Anti-Patterns

  • Using SWR for client-only state: SWR is for server state; use useState/Zustand for local state
  • Disabling all revalidation: Defeats the purpose; keep at least one revalidation trigger
  • String concatenation for complex keys: Use arrays like ["/api/products", { page, filter }]
  • Ignoring the error state: Always handle errors; SWR retries but errors can persist

When to Use

  • React applications needing simple, lightweight data fetching with caching
  • Real-time dashboards that benefit from focus-based revalidation
  • Projects already on the Vercel/Next.js ecosystem wanting consistency
  • Small to medium apps where React Query's API surface is overkill
  • Pages needing polling, conditional fetching, or dependent requests

Install this skill directly: skilldb add caching-services-skills

Get CLI access →