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:
| Feature | SWR | TanStack Query |
|---|---|---|
| Bundle size | 4KB | 12KB |
| Learning curve | Simpler | More features, steeper |
| Pagination | Basic | Advanced (infinite queries) |
| Devtools | No | Yes |
| Best for | Simple apps, Next.js projects | Complex 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:
- Returns cached data first, fetches fresh data in background
- Use
mutatefor optimistic updates - Configure globally with
SWRConfig - Create custom hooks for reusability
- 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 ?�으�?검?�하?�면 ??글???��????�니??