본문으로 건너뛰기
Previous
Next
SWR Complete Guide | React Data Fetching by Vercel

SWR Complete Guide | React Data Fetching by Vercel

SWR Complete Guide | React Data Fetching by Vercel

이 글의 핵심

SWR (stale-while-revalidate) is a React Hooks library for data fetching by Vercel. It provides caching, revalidation, focus tracking, and real-time updates out of the box.

Introduction

SWR is a React Hooks library for data fetching created by Vercel (the team behind Next.js). The name comes from stale-while-revalidate, an HTTP caching strategy that returns cached data immediately while fetching fresh data in the background.

Developed by Vercel’s Shu Ding (also creator of Nextra docs), SWR has become one of the most popular data fetching libraries in the React ecosystem, especially within the Next.js community.

The Problem

Traditional data fetching:

useEffect(() => {
  setLoading(true);
  fetch('/api/user')
    .then(res => res.json())
    .then(data => {
      setData(data);
      setLoading(false);
    });
}, []);

Problems:

  • Manual loading state
  • No caching
  • No revalidation
  • Boilerplate code

The Solution

With SWR:

const { data, error, isLoading } = useSWR('/api/user', fetcher);

Real-World Adoption and Performance

SWR is trusted by major companies and projects:

  • Vercel Dashboard ??uses SWR for all data fetching
  • Next.js documentation ??examples use SWR by default
  • TikTok ??adopted SWR for their web platform
  • Twitch ??uses SWR for real-time data updates
  • ~2.5 million weekly npm downloads (as of 2026)

Why developers choose SWR:

  • Lightweight: 4KB gzipped (TanStack Query is ~12KB)
  • Built by Vercel: Guaranteed Next.js compatibility
  • Zero config: Works out of the box with sensible defaults
  • Real-time ready: Built-in support for polling and focus revalidation

SWR vs TanStack Query comparison:

FeatureSWRTanStack Query
Bundle size4KB12KB
Learning curveSimplerMore features, steeper
PaginationBasicAdvanced (infinite queries)
DevtoolsNoYes
Best forSimple apps, Next.js projectsComplex data requirements

When to use SWR:

  • Building with Next.js (natural fit)
  • Need lightweight solution (<10KB total JS)
  • Simple CRUD operations
  • Real-time features (focus tracking, polling)

When to use TanStack Query instead:

  • Need advanced features (infinite scroll, prefetching, query invalidation)
  • Want dev tools for debugging
  • Complex pagination patterns
  • Already using React Router or other non-Next.js setup

1. Installation

npm install swr

2. Basic Usage

Simple Fetching

import useSWR from 'swr';

// Fetcher function
const fetcher = (url: string) => fetch(url).then(res => res.json());

function Profile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher);
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Failed to load</div>;
  
  return <div>Hello, {data.name}!</div>;
}

With TypeScript

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

function Profile() {
  const { data, error, isLoading } = useSWR<User>(
    '/api/user',
    fetcher
  );
  
  return <div>{data?.name}</div>;
}

3. Global Configuration

// app/layout.tsx (Next.js)
import { SWRConfig } from 'swr';

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <SWRConfig
      value={{
        fetcher: (url: string) => fetch(url).then(res => res.json()),
        revalidateOnFocus: false,
        revalidateOnReconnect: true,
      }}
    >
      {children}
    </SWRConfig>
  );
}

Now you don’t need to pass fetcher every time:

const { data } = useSWR('/api/user'); // Fetcher is global

4. Mutations

Basic Mutation

import useSWR, { mutate } from 'swr';

function UpdateProfile() {
  const { data } = useSWR('/api/user', fetcher);
  
  async function updateUser(newName: string) {
    // Update API
    await fetch('/api/user', {
      method: 'PATCH',
      body: JSON.stringify({ name: newName }),
    });
    
    // Revalidate
    mutate('/api/user');
  }
  
  return <button onClick={() => updateUser('New Name')}>Update</button>;
}

Optimistic Updates

import useSWR, { mutate } from 'swr';

function TodoList() {
  const { data: todos } = useSWR<Todo[]>('/api/todos', fetcher);
  
  async function addTodo(title: string) {
    const newTodo = { id: Date.now(), title, completed: false };
    
    // Optimistic update
    mutate(
      '/api/todos',
      async (currentTodos) => {
        // Update UI immediately
        const updatedTodos = [...(currentTodos || []), newTodo];
        
        // Send request
        await fetch('/api/todos', {
          method: 'POST',
          body: JSON.stringify(newTodo),
        });
        
        // Return optimistic data
        return updatedTodos;
      },
      {
        optimisticData: [...(todos || []), newTodo],
        rollbackOnError: true,
      }
    );
  }
  
  return (
    <div>
      {todos?.map(todo => <div key={todo.id}>{todo.title}</div>)}
    </div>
  );
}

Bound Mutate

import useSWR from 'swr';

function Profile() {
  const { data, mutate } = useSWR('/api/user', fetcher);
  
  async function updateUser() {
    // Update with bound mutate (no need to pass key)
    await mutate(async (user) => {
      await fetch('/api/user', { method: 'PATCH', body: {...} });
      return { ...user, name: 'Updated' };
    });
  }
  
  return <div>{data?.name}</div>;
}

5. Conditional Fetching

function UserProfile({ userId }: { userId?: number }) {
  // Only fetch if userId exists
  const { data } = useSWR(
    userId ? `/api/users/${userId}` : null,
    fetcher
  );
  
  return <div>{data?.name}</div>;
}

6. Dependent Requests

function UserPosts({ userId }: { userId: number }) {
  // First request
  const { data: user } = useSWR(`/api/users/${userId}`, fetcher);
  
  // Second request depends on first
  const { data: posts } = useSWR(
    user ? `/api/posts?userId=${user.id}` : null,
    fetcher
  );
  
  return <div>{posts?.length} posts</div>;
}

7. Pagination

function UserList() {
  const [page, setPage] = useState(1);
  const { data, error, isLoading } = useSWR(
    `/api/users?page=${page}&limit=20`,
    fetcher
  );
  
  return (
    <div>
      {data?.users.map((user: any) => (
        <div key={user.id}>{user.name}</div>
      ))}
      
      <button onClick={() => setPage(page - 1)} disabled={page === 1}>
        Previous
      </button>
      <button onClick={() => setPage(page + 1)}>Next</button>
    </div>
  );
}

8. Infinite Loading

import useSWRInfinite from 'swr/infinite';

function InfiniteList() {
  const getKey = (pageIndex: number, previousPageData: any) => {
    // Reached the end
    if (previousPageData && !previousPageData.hasMore) return null;
    
    // First page
    return `/api/users?page=${pageIndex + 1}&limit=20`;
  };
  
  const { data, size, setSize, isLoading } = useSWRInfinite(
    getKey,
    fetcher
  );
  
  const users = data ? data.flatMap(page => page.users) : [];
  const hasMore = data?.[data.length - 1]?.hasMore;
  
  return (
    <div>
      {users.map((user) => (
        <div key={user.id}>{user.name}</div>
      ))}
      
      {hasMore && (
        <button onClick={() => setSize(size + 1)}>Load More</button>
      )}
    </div>
  );
}

9. Real-time Updates

Auto Revalidation

const { data } = useSWR('/api/data', fetcher, {
  refreshInterval: 3000, // Refresh every 3 seconds
  refreshWhenHidden: false, // Pause when tab hidden
  refreshWhenOffline: false, // Pause when offline
});

Manual Revalidation

import { useSWRConfig } from 'swr';

function RefreshButton() {
  const { mutate } = useSWRConfig();
  
  return (
    <button onClick={() => mutate('/api/user')}>
      Refresh
    </button>
  );
}

10. Error Handling

Retry on Error

const { data, error } = useSWR('/api/user', fetcher, {
  onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
    // Never retry on 404
    if (error.status === 404) return;
    
    // Only retry 3 times
    if (retryCount >= 3) return;
    
    // Retry after 5 seconds
    setTimeout(() => revalidate({ retryCount }), 5000);
  },
});

Error Handling

const { data, error } = useSWR('/api/user', fetcher, {
  onError: (error, key) => {
    console.error('SWR error:', error);
    toast.error('Failed to fetch data');
  },
  onSuccess: (data, key, config) => {
    console.log('Data loaded:', data);
  },
});

11. Prefetching

import { mutate } from 'swr';

function UserList({ users }: { users: User[] }) {
  const prefetch = (userId: number) => {
    // Prefetch user data
    mutate(
      `/api/users/${userId}`,
      fetch(`/api/users/${userId}`).then(res => res.json())
    );
  };
  
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id} onMouseEnter={() => prefetch(user.id)}>
          <Link to={`/users/${user.id}`}>{user.name}</Link>
        </li>
      ))}
    </ul>
  );
}

12. Middleware

import useSWR from 'swr';

// Logging middleware
function logger(useSWRNext: any) {
  return (key: any, fetcher: any, config: any) => {
    const swr = useSWRNext(key, fetcher, config);
    
    useEffect(() => {
      console.log('SWR Request:', key);
    }, [key]);
    
    return swr;
  };
}

// Use middleware
const { data } = useSWR('/api/user', fetcher, { use: [logger] });

13. Best Practices

1. Create Custom Hooks

// hooks/useUser.ts
export function useUser(id: number) {
  return useSWR<User>(
    id ? `/api/users/${id}` : null,
    fetcher,
    {
      revalidateOnFocus: false,
      dedupingInterval: 2000,
    }
  );
}

// Usage
const { data: user } = useUser(123);

2. Handle Loading States

function Component() {
  const { data, error, isLoading, isValidating } = useSWR(key, fetcher);
  
  // First load
  if (isLoading) return <Skeleton />;
  
  // Error state
  if (error) return <ErrorMessage error={error} />;
  
  // Background revalidation indicator
  return (
    <div>
      {isValidating && <RefreshIcon className="animate-spin" />}
      {data && <DataDisplay data={data} />}
    </div>
  );
}

3. Cache Management

import { useSWRConfig } from 'swr';

function CacheManager() {
  const { cache, mutate } = useSWRConfig();
  
  const clearCache = () => {
    // Clear specific key
    mutate('/api/user', undefined, { revalidate: false });
    
    // Clear all cache
    if (cache instanceof Map) {
      cache.clear();
    }
  };
  
  return <button onClick={clearCache}>Clear Cache</button>;
}

14. Next.js Integration

API Routes

// app/api/users/route.ts (Next.js 13+)
export async function GET() {
  const users = await prisma.user.findMany();
  return Response.json({ users });
}

Client Component

'use client';

import useSWR from 'swr';

export function UserList() {
  const { data } = useSWR('/api/users', fetcher);
  
  return (
    <ul>
      {data?.users.map((user: any) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

15. Performance Tips

Deduplication

// Multiple components can use the same key
// SWR makes only ONE request
function ComponentA() {
  const { data } = useSWR('/api/user', fetcher);
}

function ComponentB() {
  const { data } = useSWR('/api/user', fetcher); // Uses same cache
}

Focus Revalidation

const { data } = useSWR('/api/data', fetcher, {
  // Revalidate when tab/window gains focus
  revalidateOnFocus: true,
  
  // Minimum interval between revalidations
  focusThrottleInterval: 5000, // 5 seconds
});

Summary

SWR simplifies React data fetching:

  • Automatic caching with stale-while-revalidate
  • Smart revalidation on focus, reconnect, interval
  • Optimistic updates for instant UI feedback
  • Real-time support with polling and WebSocket
  • Lightweight - only 4KB gzipped

Key Takeaways:

  1. Returns cached data first, fetches fresh data in background
  2. Use mutate for optimistic updates
  3. Configure globally with SWRConfig
  4. Create custom hooks for reusability
  5. Perfect for Next.js and Vercel deployments

Next Steps:

  • Compare with [TanStack Query](/en/blog/tanstack-query-complete-guide/
  • Learn [Next.js 15](/en/blog/nextjs-15-complete-guide/
  • Build real-time apps with [React 18](/en/blog/react-18-deep-dive/

Resources:


?�주 묻는 질문 (FAQ)

Q. ???�용???�무?�서 ?�제 ?�나??

A. Complete SWR guide for React data fetching. Learn caching, revalidation, mutations, optimistic UI, and real-time updates???�무?�서????본문???�제?� ?�택 가?�드�?참고???�용?�면 ?�니??

Q. ?�행?�로 ?�으�?좋�? 글?�?

A. �?글 ?�단???�전 글 ?�는 관??글 링크�??�라가�??�서?��?배울 ???�습?�다. C++ ?�리�?목차?�서 ?�체 ?�름???�인?????�습?�다.

Q. ??깊이 공�??�려�?

A. cppreference?� ?�당 ?�이브러�?공식 문서�?참고?�세?? 글 말�???참고 ?�료 링크???�용?�면 좋습?�다.


같이 보면 좋�? 글 (?��? 링크)

??주제?� ?�결?�는 ?�른 글?�니??

  • [TanStack Query Complete Guide | React Query· Data Fetching](/en/blog/tanstack-query-complete-guide/
  • [Astro + Cloudflare Pages Blog Stack Analysis | vs Vercel](/en/blog/astro-cloudflare-pages-stack-comparison/
  • [Qwik Complete Guide | Resumable JavaScript Framework](/en/blog/qwik-complete-guide/

??글?�서 ?�루???�워??(관??검?�어)

SWR, React, Data Fetching, Vercel, Cache, Real-time, TypeScript ?�으�?검?�하?�면 ??글???��????�니??