Develop
TanStack Query 완벽 가이드 — React 서버 상태를 관리하는 방법
API 호출을 처리하는 리액트 컴포넌트 내 보일러플레이트 코드를 제거하고, 초고속 캐싱 및 데이터 갱신을 선언적으로 해소하기 위한 staleTime 튜닝 및 Optimistic Update 실전 패턴을 정리했습니다.

- ·TanStack Query는 서버 상태(서버에서 가져온 데이터)를 클라이언트에서 관리하는 것에 특화된 라이브러리다
- ·useQuery는 데이터 패칭, 캐싱, 리패칭을 자동으로 처리하고 staleTime, cacheTime으로 캐시 전략을 설정할 수 있다
- ·useMutation은 데이터 변경 작업(POST, PUT, DELETE)을 처리하며 onSuccess, onError 콜백으로 후속 처리를 할 수 있다
- ·QueryClient의 invalidateQueries로 특정 쿼리를 무효화하면 다음 렌더링 시 자동으로 새 데이터를 패칭한다
매번 `useEffect` 블록 안에서 비동기 fetch를 쏘고, API 로딩 상태와 에러 상태를 처리하기 위해 `useState` 3종 세트를 지저분하게 작성하던 과거 컴포넌트 코드 덩어리가 있었습니다. TanStack Query로 마이그레이션하자마자 이 중복 로직들이 단 5줄의 `useQuery` 구문으로 간소화되어 컴포넌트의 부피가 절반 이하로 쪼그라들었습니다. 다만 데이터 갱신의 미묘한 타이밍을 뜻하는 `staleTime` 개념을 제대로 설정하지 않았다가, 유저가 마우스 포커스를 브라우저 탭에 올릴 때마다 서버 API를 미친 듯이 리패칭하는 폭탄 호출 현상을 겪은 후, 실무 데이터의 갱신 주기 튜닝이 얼마나 정교해야 하는지 깊이 절감했습니다.
1. 서버 상태의 본질과 선언적 패칭
선언적 훅으로 React 서버 상태 관리를 단순화하는 방법
리액트 기본 구조에서 서버 데이터를 가져와 그리려면 useEffect 생명주기를 잡아야 하고, 이는 로딩 지연 처리나 에러 복구 로직이 조금만 섞여도 보일러플레이트 코드가 기하급수적으로 폭발하게 만듭니다. 게다가 다른 컴포넌트에서 동일한 데이터를 요구할 때 API가 중복으로 나가 네트워크 대역을 낭비하죠. TanStack Query는 서버 상태(Server State) 즉, 내가 제어권을 완전히 쥐고 있지 않으며 언제든 원격에서 변경될 수 있는 비동기 데이터를 전담 관리하는 특화 엔진입니다. 데이터를 컴파일해 캐시 스토어에 고이 박아두고 전역 컴포넌트들이 이를 훅으로 공유 조회하게 유도하므로, 보일러플레이트 코드 없이 명쾌하게 비동기 생태계를 다스려 줍니다.
2. staleTime 과 뮤테이션 전략
중복 조회를 방지하는 TanStack Query useQuery staleTime과 캐시 전략
useQuery 사용 시 가장 강력하면서도 다듬기 까다로운 개념은 staleTime 과 gcTime(구 cacheTime)의 조율입니다. staleTime 은 가져온 데이터가 싱싱한(fresh) 상태를 보존할 시간으로, 기본값이 0 입니다. 이 때문에 셋업을 대충 해두면, 유저가 다른 페이지로 잠시 눈을 돌렸다가 복귀할 때마다 데이터가 시든(stale) 것으로 보고 API를 재조회하죠. 자주 바뀌지 않는 카테고리나 사용자 기본 설정 데이터는 staleTime을 5분(1000 * 60 * 5) 등으로 길게 오버라이딩해 주면, 불필요한 HTTP 전송 오버헤드를 아껴줄 수 있습니다.
import { useQuery } from '@tanstack/react-query';
export function useGetUsers() {
return useQuery({
queryKey: ['users', 'list'], // 캐시를 식별하는 고유 키 매핑
queryFn: async () => {
const res = await fetch('/api/users');
if (!res.ok) throw new Error('Network response error');
return res.json();
},
staleTime: 1000 * 60 * 5, // 5분 동안 캐시 데이터를 fresh 상태로 보존
refetchOnWindowFocus: false // 브라우저 창 활성화 시 자동 리패칭 방어
});
}서버 데이터를 변경하는 TanStack Query useMutation의 기본적인 쓰임새
단순 데이터 조회를 지나 POST, PUT, DELETE 등 원격 서버의 리소스를 직접 수정하는 쓰기 작업에는 useMutation 훅이 출격합니다. 뮤테이션은 데이터를 갱신 완료한 후, 기존에 떠돌던 조회 쿼리의 캐시를 강제 휘발시켜 다음 렌더링 시점에 최신 서버 값을 자동 풀링하게 돕는 연계 플레이가 기본 공식입니다. 성공 콜백 수신부(onSuccess) 내부에서 queryClient.invalidateQueries({ queryKey: ['users'] }) 명령을 삽입해 두면, 젠킨스 배포가 돌지 않아도 화면이 스스로 싱싱한 데이터셋으로 탈바꿈을 이행합니다.
UX 반응 속도를 높이는 TanStack Query Optimistic Update 구현 패턴
모바일 네트워크 감도가 불량하거나 서버 응답이 지연될 때, 유저가 좋아요 버튼을 누르거나 목록을 삭제했을 때 로딩 스피너를 몇 초간 마주하는 것은 UX에 매우 치명적입니다. 이를 극복하는 테크닉이 바로 낙관적 업데이트(Optimistic Update) 패턴입니다. 서버가 API 성공 응답을 보내주기도 전에, **'이 요청은 무조건 성공할 것이다' 라고 먼저 낙관적으로 캐시 데이터를 미리 고쳐서 화면에 뿌리는 방식**입니다. onMutate 콜백 내부에 이전 상태를 스냅샷으로 백업해 두고 캐시를 강제 업데이트하며, 만에 하나 서버가 실패 에러를 뱉었을 경우 onError 단에서 백업된 스냅샷으로 원복(Rollback)시키는 설계를 묶어주어 사용자 반응 응답 체감을 경이롭게 격상시킵니다.
3. 전역 인젝션 및 모듈화
Next.js 환경에 맞춘 QueryClient 설정과 Provider 셋업 요령
애플리케이션에 쿼리 엔진을 이식하려면 최상위 루트에 QueryClientProvider 를 휘감아 주어야 합니다. Next.js App Router 런타임 하단에서는 layout.tsx 파일이 서버 컴포넌트 규칙을 따르므로, Provider 셋업에 필요한 리액트 컨텍스트 API가 충돌을 뿜습니다. 해결책은 파일 최상단에 'use client' 지시어를 할당한 별도 Custom Provider 랩 래퍼 컴포넌트를 만들고, QueryClient 인스턴스가 렌더링 시마다 리셋되는 현상을 막기 위해 useState 내에 인스턴스 생성을 격리하는 디자인 구조를 수립하는 것입니다.
// providers/QueryProvider.tsx - NextJS SSR 환경에 호환되는 QueryClient 설정
'use client';
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
export default function QueryProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60, // 전역 기본 staleTime 1분 지정
retry: 1 // API 에러 시 재시도 횟수 제한
},
},
})
);
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}도메인 단위로 쿼리 로직을 격리하여 React 서버 상태 관리하기
useQuery 나 useMutation 호출문을 개별 UI 컴포넌트 소스 파일 안에 지저분하게 나열하게 되면, 쿼리 키가 중복되거나 로직 변경 시 모든 컴포넌트 파일을 수정하러 돌아다녀야 합니다. 이를 방어하려면 도메인별로 커스텀 훅 파일(useUsers.ts, usePosts.ts)을 격리하고 훅 내부에서만 Query API를 호출하도록 캡슐화해야 합니다. 컴포넌트는 오직 훅이 전달하는 깨끗한 데이터 알맹이만 받아 렌더링에만 집중하도록 역할을 정돈해 두는 것이 대형 프론트엔드 프로젝트 유지 보수의 지름길입니다.
자주 묻는 질문
useQuery로 조회를 돌리는데 특정 데이터가 채워지기 전에는 API 실행을 방지(조건부 패칭)하고 싶어요.+
useQuery 옵션 파라미터 내부에 `enabled` 속성을 지정하고 boolean 상태 값을 넘겨주시면 됩니다. 예를 들어 `enabled: !!userId` 형태로 기입해 두면, userId 데이터가 안전하게 식별되기 전까지는 네트워크 쿼리 발송 자체를 젠킨스가 아닌 브라우저 단에서 완전 차단해 줍니다.
Zustand 전역 상태관리 툴과 TanStack Query는 서로 대체 관계인가요?+
대체제가 아닌 상호 보완재입니다. TanStack Query는 백엔드 서버에서 긁어온 데이터(Server State)의 비동기 캐싱과 동기화를 제어하는 데 집중하고, Zustand는 화면 토글 유무나 모달 창 상태 등 클라이언트 단 전용 상태(Client State)를 관리하는 데 활용해 역할을 명확히 2분할하는 것이 정석 실무 아키텍처입니다.
관련 글
Next.js App Router fetch 캐싱과 revalidate 완벽 가이드 — 언제 데이터가 갱신되나
Next.js App Router 환경에서 fetch API가 수행하는 캐싱 로직과 데이터를 원하는 시점에 갱신하기 위한 cache 옵션, next.revalidate, on-demand 무효화(revalidatePath/revalidateTag) 메커니즘을 상세히 분석합니다.
React useMemo, useCallback 성능 최적화 — 렌더링 최소화하는 방법
리액트 컴포넌트의 렌더링 지연을 해소하기 위한 useMemo와 useCallback 훅의 올바른 작동 기제와, 측정 없이 남용했다가 되려 메모이제이션 오버헤드로 앱 성능을 망가뜨리는 함정의 극복 방안을 알아봅니다.
Next.js 환경변수 완벽 가이드 — .env.local부터 NEXT_PUBLIC 클라이언트 변수까지
Next.js에서 API 키는 서버에서만 써야 하므로 NEXT_PUBLIC 없이, 브라우저에서도 써야 하면 NEXT_PUBLIC를 붙여야 한다. .env 파일 종류, 서버/클라이언트 변수 구분, 환경별 설정까지 정리했다.