Skip to content

[리팩토링] tanstackquery cache 문제 해결 방안 #296

@shinwokkang

Description

@shinwokkang

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and read key files
echo "=== useNotificationQueries.ts ==="
fd "useNotificationQueries" --exec cat {}

echo "=== useAuthStore ==="
fd "useAuthStore" --exec cat {}

echo "=== QueryClient / Provider setup ==="
fd -e ts -e tsx "queryClient\|QueryClient\|ReactQueryProvider\|TanstackProvider\|queryProvider" -i --exec cat {}

echo "=== logout related logic ==="
rg -n "logout\|signOut\|clearCache\|queryClient.clear\|removeQueries\|invalidateQueries" --type=ts --type=tsx -C2

echo "=== Header.tsx ==="
fd "Header.tsx" --exec cat {}

echo "=== NotificationDropdown.tsx ==="
fd "NotificationDropdown.tsx" --exec cat {}

Length of output: 18608


@shinwokkang 코드베이스를 분석한 결과, TanStack Query 캐시에 이전 사용자 데이터가 잔류할 수 있는 문제점들을 아래와 같이 정리했습니다.


🔍 발견된 문제 목록

1. 🚨 [가장 심각] 로그아웃 시 캐시를 초기화하지 않음

useAuthStorelogout() 함수는 쿠키 제거와 Zustand 상태 리셋만 수행합니다.

// src/store/useAuthStore.ts
logout: () => {
  Cookies.remove("accessToken");
  set({ user: null, isLoggedIn: false, isInitialized: true });
  // ❌ queryClient.clear() 또는 removeQueries() 호출 없음
},

TanStack Query의 기본 gcTime(구 cacheTime)은 5분입니다. 즉, 로그아웃 후 5분 이내에 다른 사용자가 로그인하면, 이전 사용자의 알림 데이터가 캐시에 그대로 남아있어 새 사용자에게 노출될 수 있습니다.

해결책: logout()queryClient.clear() 호출

// useAuthStore.ts 수정 예시
import { getQueryClient } from "@/lib/queryClient"; // QueryClient 싱글톤 참조

logout: () => {
  Cookies.remove("accessToken");
  set({ user: null, isLoggedIn: false, isInitialized: true });
  getQueryClient().clear(); // ✅ 모든 캐시 제거
},

2. 🚨 사용자 식별자가 없는 쿼리 키

현재 모든 알림 관련 쿼리 키에 사용자 ID가 포함되지 않습니다.

// src/hooks/queries/useNotificationQueries.ts
export const notificationKeys = {
  all: ["notifications"] as const,
  settings: () => [...notificationKeys.all, "settings"] as const,
  infiniteList: () => [...notificationKeys.all, "infiniteList"] as const,
  preview: (size?: number) => [...notificationKeys.all, "preview", size] as const,
  // ❌ 예: ["notifications", "preview", 5] → 사용자 구분 없음
};

사용자 A가 로그아웃하고 사용자 B가 로그인하더라도 쿼리 키가 동일하기 때문에, TanStack Query는 기존 캐시를 그대로 재사용합니다. 위 Issue 1이 해결되어도, 이 구조가 유지되는 한 근본적으로 안전하지 않습니다.

해결책: 쿼리 키에 userId 포함

export const notificationKeys = {
  all: (userId: string) => ["notifications", userId] as const,
  preview: (userId: string, size?: number) =>
    [...notificationKeys.all(userId), "preview", size] as const,
  // ...
};

3. ⚠️ useNotificationSettingsQuery, useInfiniteNotificationsQueryenabled 가드 없음

이번 PR에서 useNotificationPreviewQuery에는 enabled 옵션이 추가되었지만, 나머지 두 쿼리에는 아직 적용되지 않았습니다.

// ❌ enabled 옵션 없음 → 비로그인 상태에서도 요청 발생
export const useNotificationSettingsQuery = () => {
  return useQuery({
    queryKey: notificationKeys.settings(),
    queryFn: () => notificationService.getSettings(),
    // enabled 없음
  });
};

export const useInfiniteNotificationsQuery = () => {
  return useInfiniteQuery({
    queryKey: notificationKeys.infiniteList(),
    queryFn: ...,
    // enabled 없음
  });
};

4. ⚠️ 로그인 직후 이전 사용자 데이터 순간 노출 (Flash of Wrong Content)

Issue 1, 2가 해결되지 않은 상태에서, 사용자 B가 로그인하면:

  1. TanStack Query가 캐시에서 사용자 A의 데이터를 즉시 렌더링 (stale-while-revalidate 동작)
  2. 백그라운드에서 사용자 B의 데이터를 fetch
  3. 응답이 오기 전까지 사용자 A의 알림 내용이 화면에 표시됨

hasUnread 레드닷도 이전 사용자의 읽지 않은 알림 여부를 기준으로 잘못 표시될 수 있습니다.


✅ 권장 수정 요약

문제 위치 수정 방법
로그아웃 시 캐시 미정리 useAuthStore.ts logout()queryClient.clear() 추가
사용자 미식별 쿼리 키 useNotificationQueries.ts 키에 userId 포함
enabled 가드 누락 useNotificationQueries.ts useNotificationSettingsQuery, useInfiniteNotificationsQuery에도 enabled 추가

가장 빠른 해결은 logout() 함수에서 queryClient.clear()를 호출하는 것입니다. queryClient를 Zustand store 외부에서 접근하려면 싱글톤 패턴으로 QueryClient 인스턴스를 관리하거나, 로그아웃 처리 로직을 컴포넌트 레이어로 올려서 useQueryClient() 훅을 활용하는 방법을 고려해 보세요.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions