0ju-log
💻 Frontend

[Library] RTK Query VS. Tanstack Query

1️⃣ 이 주제로 글을 쓰는 이유

회사에서 웹과 앱을 각각 다른 스택으로 운영하고 있었고, 서버 상태 관리는 웹은 TanStack Query, 앱은 RTK Query를 사용하고 있었다.

이 상태에서 모노레포 도입을 고려하게 되었고, 공통 API / 인증 로직을 어떻게 가져가야 할지가 문제였다.

이로 인해 하나의 라이브러리로 통합하려는 고민과 어떤 선택이 장기적으로 더 나은지 고민하게 되었다.

2️⃣ RTK Query vs Tanstack Query

이번 선택에서 중요하게 본 기준은 다음과 같았다.

  • 웹 / 앱 구조 차이에 대한 유연성
  • 서버 상태와 UI 상태의 분리 정도
  • 기존 코드베이스와의 정합성
  • 모노레포 환경에서의 코드 공유 용이성

각 라이브러리 정의


RTK Query

  • Redux와 깊게 통합되어 전역 상태 관리를 하면서 데이터를 가져오고 동기화함
  • createApi를 통해 API Slice를 정의하며, 이 slice는 자동으로 데이터 요청/조작용 훅 생성
    • 훅 : ex. useLoginMutation, useFetchPowerplantQuery

Tanstack Query

  • Tanstack Query는 상태 관리 라이브러리와 독립적으로 동작하며, 컴포넌트 수준에서 캐싱과 서버 상태 관리를 처리함
  • queryKey, select, invalidateQueries 등을 제공함
  • axios와 같은 요청 라이브러리를 직접 사용하여 요청 로직을 구성함
RTK QueryTanstack Query
상태 관리Redux가 제공하는 기능 중 하나.
Redux가 적용된 프로젝트에 추가하기 쉬움상태를 저장하는 저장소가 따로 존재하지 X,
Redux가 아닌 여러 상태 관리 라이브러리와 호환하여 사용 가능
API 호출법createAPI 메서드 사용useQuery, useMutation 메서드 사용
데이터 정규화createAPI에서 데이터를 정규화하여 상태 관리 최적화데이터를 자동으로 정규화하지는 않으나
API의 요청 중복 방지 및 캐싱을 통해 성능 향상
캐싱 및 태그 관리providesTags와 invalidatesTags를 통해 데이터 변경 시 쿼리 데이터를 자동으로 갱신함
철학중앙 집중식 엔드포인트 정의컴포넌트 단위의 선언적 페칭
Github Stars11.2k48.6k
번들 사이즈13.4KB13.3KB

📜1️⃣ RTK Query로 통합하는 경우


개요

  • 서버 상태 관리를 RTK Query + Redux Store 기준으로 통합
  • 앱은 기존 구조 유지
  • 웹은 기존 Tanstack Query 기반 API 로직을 RTK Query로 전환

적합한 경우

  • Redux를 팀 표준 상태 관리로 강하게 가져가려는 경우
  • 앱 중심 구조이며 웹은 상대적으로 단순한 경우
  • API 구조 변경 가능성이 낮은 내부 관리용 서비스

장점


  • Redux 기반 전역 상태 관리에 익숙할 경우, 효율 증가
  • 앱 쪽 추가 작업이 거의 없음

단점


  • 웹에서 이미 사용 중인 Tanstack 생태계와 철학적 불일치
    • Tanstack Table / Virtual과 데이터 연결 구조가 어색해질 수 있음
  • 웹의 기존 API 코드 마이그레이션 비용이 큼
  • Redux 의존도가 웹까지 확대됨
  • 서버 상태와 UI 상태가 Redux에 함께 쌓이며 장기적으로 store의 복잡도 증가 우려됨
  • 모노레포 환경에서
    • store 구조 차이로 공통 API 로직 재사용이 어려워질 가능성

📜2️⃣ TanStack Query로 통합하는 경우


개요

  • 서버 상태 관리를 Tanstack Query + Zustand 기준으로 통합
  • 웹은 기존 구조 유지
  • 앱은 RTK Query 기반 API 로직을 Tanstack Query로 전환

적합한 경우

  • 테이블 / 리스트 / 대용량 데이터 중심 서비스
  • 웹과 앱을 동등하게 혹은 웹 중심 구조일 경우
  • 장기적인 확장성과 유연성을 중시하는 경우

장점


  • 웹에서 이미 사용 중인 Tanstack Table / Virtual과 라이브러리 생태계 일치 → 통일성 증가, 데이터 연결 자연스럽게 연계 가능
  • 모노레포에서
    • API 함수 / query key / 훅을 패키지 단위로 공유하기 쉬움
  • 앱 번들 크기 및 구조 단순화

단점


  • 앱에서 RTK Query 제거 및 마이그레이션 비용 발생
  • Redux 기반 패턴에 익숙할 경우 초기 적응 비용 존재

3️⃣ 웹, 앱 인증 및 토큰 로직 통합 플랜

1. 방식 설정


1. 웹과 앱을 같은 로그인 상태로 묶고 싶은가?

  • 웹에서 로그아웃 → 앱에서도 자동 로그아웃
  • 하나의 세션으로 관리

2. 웹과 앱을 독립적인 로그인 세션으로 가져가고 싶은가?

  • 웹과 앱이 다른 쿠키를 쓰도록 분리시킴

⇒ 2번 방안으로 확정

2. 통합 규칙


  1. 쿼리 라이브러리, 전역 상태 관리 라이브러리 통일

    : RTK Query + Redux VS. Tanstack Query + Zustand

    • 웹에서 사용하는 토큰 관리 방식

      • zustand : accessToken, sessionReady
      • axios interceptor : 토큰 부착 + refresh
      • custom hook
    • 앱에서 사용하는 토큰 관리 방식

      login mutation
        ↓
      onQueryStarted
        ↓
      setAccessToken(accessToken)
      
  2. Middleware는 유지

3. 개발 플랜


  1. accessToken 저장소 통일하기
  2. 웹의 인증 API 단계 통일하기
    • 인증 API : login, refresh, logout
  3. 세션 초기화 로직을 웹 + 앱 하나로 통합

4️⃣ 결론


RTK Query로의 통합은 단기적으로 앱 구조와의 정합성이 높으나,
이미 웹이 TanStack 생태계를 중심으로 확장되고 있는 상황에서는
웹의 구조적 변경 비용과 장기 유지보수 리스크가 존재한다.

반면 TanStack Query 기준 통합은 앱의 마이그레이션 비용이 발생하지만, 웹과 앱 모두에서 서버 상태 관리 패턴을 통일할 수 있으며 모노레포 운영과 장기 확장성 측면에서 더 안정적인 선택지라고 판단했다.

Tanstack Query를 선택하게 된 이유

개인적으로 Tanstack Query가 선택하는 것이 더 좋다고 생각하였고, 이에 대한 나의 의견을 작성해보았다.


  1. 현재 웹에서 Tanstack Query뿐만 아니라 Tanstack Virtual, Tanstack Table을 사용 중

    • API 호출만 RTK Query를 사용할 경우, 프론트엔드 전반의 일관성이 떨어짐
    • TanStack Table의 필터/정렬 상태를 Tanstack Query의 키값으로 연결해 서버 데이터를 페칭하는 구조를 모노레포 전체에서 재사용하기 유리해짐
  2. Zustand 용량 <<< Redux Toolkit 용량

    • 앱 환경에서는 번들 크기와 메모리 관리가 중요함
    • 상대적으로 경량화된 라이브러리인 zustand를 사용하는 게 앱의 특성에 있어 적합함
    라이브러리redux-toolkitzustand
    용량5.51MB327KB
    • 앱의 기존 Redux 로직도 Zustand를 통해 점진적으로 단순화하면 전체적인 코드 베이스가 가벼워질 것으로 예상됨
  3. 모노레포에서의 코드 공유

    • 모노레포의 핵심 : packages/api 와 같은 공통 로직을 분리하는 것
    • Tanstack Query : API 요청 함수와 커스텀 훅을 순수하게 정의 → 웹과 앱에서 그대로 import하여 유연하게 사용 가능
    • RTK Query : createApi로 정의된 서비스 객체가 Redux store 구성 방식에 강하게 결합 → 플랫폼별로 store 구조가 다를 경우 공유 로직 작성이 까다로움

5️⃣ 참고

6️⃣ 번외

➕ 시나리오 : Redux/Toolkit + Tanstack Query

개요

  1. 서버 상태는 Tanstack Query로, 클라이언트/전역 상태는 RTK를 사용함
    • 서버 데이터 : Tanstack Query를 통해 가져옴
    • UI 상태 / 전역 상태 (폼, 모달, 필터, 탭) : RTK에서 slice로 관리
  2. Redux를 “전역 UI 상태 전용”으로 최소화
    • 서버 상태를 Redux로까지 끌고 오는 경우는 불필요한 boilerplate + 동기화 버그 발생 가능
    • TanStack Query가 이미 컴포넌트 레벨 캐시를 제공하기 때문에, 대부분 UI 상태만 Redux로 유지
// Redux: filter 상태
const filter = useSelector((state) => state.ui.filter);

// TanStack Query: 서버 데이터
const { data: users } = useQuery(['users'], fetchUsers);

// 결합
const filteredUsers = users?.filter(u => u.role === filter);

장점


  • 서버 상태와 클라이언트 상태의 역할이 명확함
    • 각 라이브러리가 잘하는 역할에만 집중 → 구조 이해가 쉬움
  • 대규모 서비스에서 확장성 좋음
    • UI 상태는 전역으로 공유하면서도 서버 상태는 컴포넌트 단위로 독립적으로 관리 가능
    • 팀 단위 협업 시 책임 범위가 명확해짐
  • 불필요한 Redux boilerplate 감소
    • async thunk, loading/error 상태 관리 코드가 줄어듦
    • Redux는 순수 상태 관리에만 사용되어 코드가 가벼워짐
    • 참고 블로그 : https://mkdiriandev.tistory.com/4

단점


  • 상태 관리 도구가 2개라 러닝 커브가 있음
    • Redux Toolkit + TanStack Query 모두에 대한 이해 필요
  • Redux를 쓰는 이유가 약해질 수 있음
    • 전역 UI 상태가 많지 않은 서비스라면 굳이 Redux 쓸 필요 X
  • 역할 분리가 무너지면 오히려 혼란
    • Redux와 Query를 사용하는 기준이 없으면 상태가 중복되거나 책임이 섞일 위험 있음

주의할 점

  • Tanstack Query로 fetch한 데이터를 Redux store에 복사하지 말 것

    → 중복 캐시 + 상태 동기화 문제 발생 가능

  • 명확한 역할 분리

➕ tanstack query + zustand 에서 전역 store 파일 하나로 공유하여 사용할 수 있을까?

import { create } from "zustand";

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

// 컴포넌트 어디서든
const count = useStore((state) => state.count);

장점

  • boilerplate 없이 전역 상태 생성

    • Redux처럼 Provider/Context/Reducer/Action 구조가 필요 없고, 단일 훅으로 상태와 수정 함수를 정의해서 사용 가능

      → useStore()를 호출하는 모든 컴포넌트는 같은 전역 store에 접근!

  • selector 기반 구독으로 성능 최적화

    • Zustand는 selector를 지원하므로, 필요한 상태만 구독하여 불필요한 리렌더 감소시킴

➕ 쿼리 + 전역 상태 조합 비교

📍 작고 빠른 SPA / 팀 소규모 / 단순 UI 로직

👉 <strong>TanStack Query + Zustand</strong> 

📍 중대형 앱 / 구조화된 전역 상태 필요

👉 <strong>TanStack Query + Redux Toolkit</strong>

📍 대형 조직 / Redux 중심 아키텍처

👉 <strong>RTK Query + Redux Toolkit</strong>