💡 핵심 요약 (TL;DR)

Next.js 서버 환경에서 QueryClient를 전역 변수(싱글톤)로 선언하면 **모든 접속자의 데이터가 공유되는 치명적인 보안 사고(Cross-Request State Leak)**가 발생합니다. 이를 막기 위해 서버에 요청이 들어올 때마다 새로운 QueryClient 인스턴스를 생성하는 팩토리(Factory) 함수를 사용해야 합니다.

1. 🚨 전역 변수(Singleton)의 치명적인 위험성

기존 React(SPA) 앱에서는 클라이언트(브라우저) 환경만 고려하면 되었기 때문에 QueryClient를 파일 최상단에 딱 한 번만 선언해서 사용했습니다.

하지만 Next.js의 서버 컴포넌트(RSC) 환경에서는 이 방식이 매우 위험합니다.

⚠️ Cross-Request State Pollution (요청 간 상태 오염)

서버에 QueryClient를 전역으로 하나만 만들어 두면 다음과 같은 대참사가 발생할 수 있습니다.

  1. 사용자 A[내 정보 보기] 페이지를 요청하여 A의 개인정보가 서버의 QueryClient 캐시에 저장됨.
  2. 1초 뒤 사용자 B가 똑같이 [내 정보 보기] 페이지를 요청함.
  3. 서버는 캐시에 이미 데이터가 있는 것을 보고, B에게 A의 개인정보를 보여줌!

2. ✅ 해결책: 요청(Request)마다 새로운 인스턴스 생성

이러한 상태 누수를 원천 차단하려면, 서버에 요청이 들어올 때마다 완전히 새롭고 텅 빈 QueryClient 인스턴스를 생성해야 합니다.

// src/utils/react-query/client.ts
import { QueryClient } from "@tanstack/react-query";

/** RSC용 QueryClient 팩토리 — page.tsx에서 prefetchQuery 시 사용 */
export function createQueryClient() {
  // 함수가 호출될 때마다 해당 요청(사용자)만을 위한 '새로운' 인스턴스가 반환됨
  return new QueryClient({ ... }); 
}

서버 렌더링이 끝나고 클라이언트로 데이터를 넘겨주고(Dehydration) 나면, 이 일회용 캐시 바구니는 안전하게 폐기되어 다른 사용자와 데이터가 섞일 일이 없습니다.

📖 TanStack Query 공식 문서 발췌

"컴포넌트 생명주기 밖에서 QueryClient를 생성하면 싱글톤이 됩니다. 이는 캐시가 모든 사용자와 요청 간에 공유된다는 것을 의미하며, **요청 간 상태 오염(Cross-Request State Pollution)**으로 이어집니다. 대신, 항상 컴포넌트 생명주기 내에서(또는 요청당) 새로운 QueryClient를 생성해야 합니다."

3. ⚙️ 디테일 분석: SSR 맞춤형 defaultOptions

팩토리 함수 내부에 설정된 옵션들은 서버사이드 렌더링(SSR) 환경을 위한 아주 디테일한 최적화 설정입니다.

defaultOptions: {
  queries: {
    staleTime: 60 * 1000, // 1분
    retry: 1,
  },
},