Web/Next&React

Tanstack Query

창욱씨 2026. 9. 13. 21:05
반응형

TanStack Query

서버에서 가져온 데이터를 브라우저 메모리에 캐싱하고, 그 캐시의 생명주기를 자동으로 관리해주는 프론트엔드 라이브러리

"서버 상태 관리"라는 표현이 흔히 쓰이지만, 실제로는 비동기 데이터 캐시 자동 관리가 더 정확한 설명이다.


어디서 동작하는가

브라우저(클라이언트)에서 동작한다. Node.js 서버가 아니다.

┌──────────────────┐       HTTP 요청        ┌──────────────┐
│     브라우저       │ ───────────────────▶  │   백엔드 서버   │
│                  │                        │              │
│  React 앱        │ ◀───────────────────   │  (아무 서버)   │
│  + TanStack Query│      JSON 응답         │              │
│  (여기서 동작!)    │                        │              │
└──────────────────┘                        └──────────────┘
  캐시 저장/관리                                DB 조회/응답
  리페치 판단
  로딩/에러 추적

백엔드 서버는 평소처럼 API 응답만 해주면 되고, 서버 쪽에서 바뀌는 건 없다.


캐시 저장 위치

JavaScript 메모리(RAM)에 저장된다. QueryClient 객체 내부에 Map 형태로 존재한다.

const queryClient = new QueryClient();

// 내부적으로 대략 이런 구조
{
  ['todos']:    { data: [...], dataUpdatedAt: 1694600000000 },
  ['user', 1]:  { data: {...}, dataUpdatedAt: 1694600050000 },
}
저장 장소 아닌 것
✅ JS 메모리 (QueryClient 객체) ❌ DOM / 화면
❌ localStorage / sessionStorage
❌ 서버

새로고침하면 캐시는 전부 사라진다. (필요하면 persistQueryClient 플러그인으로 영속화 가능)


동작 원리: 이벤트 기반 리페치

타이머로 주기적으로 요청하지 않는다. 특정 이벤트가 발생했을 때, 데이터가 stale(오래된) 상태이면 다시 요청한다.

리페치를 트리거하는 이벤트

이벤트 설명 기본값
컴포넌트 마운트 화면에 나타날 때 ✅ 켜짐
윈도우 포커스 다른 탭 갔다가 돌아올 때 ✅ 켜짐
네트워크 재연결 와이파이 끊겼다 연결될 때 ✅ 켜짐
수동 무효화 invalidateQueries 호출 개발자가 직접
refetchInterval 주기적 폴링 (옵트인) ❌ 꺼짐

staleTime이 핵심

staleTime: 0 (기본값)
  → 가져온 즉시 stale 취급 → 이벤트마다 리페치

staleTime: 5분
  → 5분간 fresh → 이벤트가 발생해도 리페치 안 함

시나리오 예시

1. 할일 목록 페이지 진입 → 마운트 → 캐시 없음 → 서버 요청 → 저장
2. 다른 페이지 갔다가 돌아옴 → 마운트 → 캐시 있음
   → fresh면 캐시 반환 (요청 안 함)
   → stale이면 캐시 먼저 보여주고 백그라운드에서 서버 요청
3. 다른 탭 갔다가 돌아옴 → 포커스 → stale이면 리페치
4. 새 할일 추가 → invalidateQueries → 즉시 리페치

주요 API

useQuery — 데이터 조회 (Read)

캐싱, 자동 리페치, 중복 제거 등을 담당한다.

const { data, isLoading, error } = useQuery({
  queryKey: ['todos'],
  queryFn: () => fetch('/api/todos').then(r => r.json()),
});

useMutation — 데이터 변경 (Create / Update / Delete)

캐싱은 없다. 변경 요청 후 캐시를 어떻게 갱신할지 관리한다.

const mutation = useMutation({
  mutationFn: (newTodo) => axios.post('/api/todos', newTodo),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

mutation.mutate({ title: '새 할일' });

조회만 되는 게 아니다

용도 Hook
조회 (Read) useQuery
변경 (Create/Update/Delete) useMutation

둘이 세트로 사용하는 게 일반적이다. 변경 후 invalidateQueries로 조회 캐시를 갱신하는 패턴.


적용 범위

서버 액션 전용이 아니다. Promise를 반환하는 아무 비동기 함수와 동작한다.

// fetch, axios, GraphQL, 로컬 DB 등 뭐든 가능
useQuery({
  queryKey: ['todos'],
  queryFn: () => 아무_async_함수(),
});

Next.js App Router와의 관계

기능이 겹친다

기능 TanStack Query Next.js App Router
데이터 조회 useQuery 서버 컴포넌트 + fetch
데이터 변경 useMutation 서버 액션 + useActionState
캐시 무효화 invalidateQueries revalidatePath / revalidateTag
로딩 상태 isPending useFormStatus

실제로는?

둘 중 하나만 쓰거나, 역할을 나눠서 같이 쓰기도 한다.

상황 추천
정적 페이지, 블로그, 초기 데이터 로드 App Router만으로 충분
무한 스크롤, 실시간 검색, 폴링 등 인터랙티브 기능 TanStack Query 같이 사용
Next.js 안 씀 (Vite, CRA 등) TanStack Query 거의 필수

둘을 섞으면 캐시 관리 포인트가 두 개가 되므로, 프로젝트 성격에 맞게 취사선택하는 것이 중요하다.


핵심 포인트 요약

질문 답
뭐 하는 라이브러리? 비동기 데이터 캐시 자동 관리
어디서 동작? 브라우저 (클라이언트)
캐시는 어디에? JS 메모리 (새로고침 시 사라짐)
주기적으로 요청? ❌ 이벤트 기반 (마운트, 포커스, 네트워크 등)
조회만? ❌ 변경도 가능 (useMutation)
서버 액션 전용? ❌ 아무 async 함수와 동작
SSR? ❌ 클라이언트 캐시 라이브러리 (SSR 지원은 가능)
Next.js App Router와? 겹치는 부분 있어서 취사선택
반응형