💡 핵심 요약 (TL;DR)
Next.js 서버 환경에서
QueryClient를 전역 변수(싱글톤)로 선언하면 **모든 접속자의 데이터가 공유되는 치명적인 보안 사고(Cross-Request State Leak)**가 발생합니다. 이를 막기 위해 서버에 요청이 들어올 때마다 새로운QueryClient인스턴스를 생성하는 팩토리(Factory) 함수를 사용해야 합니다.
기존 React(SPA) 앱에서는 클라이언트(브라우저) 환경만 고려하면 되었기 때문에 QueryClient를 파일 최상단에 딱 한 번만 선언해서 사용했습니다.
하지만 Next.js의 서버 컴포넌트(RSC) 환경에서는 이 방식이 매우 위험합니다.
서버에 QueryClient를 전역으로 하나만 만들어 두면 다음과 같은 대참사가 발생할 수 있습니다.
사용자 A가 [내 정보 보기] 페이지를 요청하여 A의 개인정보가 서버의 QueryClient 캐시에 저장됨.사용자 B가 똑같이 [내 정보 보기] 페이지를 요청함.이러한 상태 누수를 원천 차단하려면, 서버에 요청이 들어올 때마다 완전히 새롭고 텅 빈 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를 생성해야 합니다."
defaultOptions팩토리 함수 내부에 설정된 옵션들은 서버사이드 렌더링(SSR) 환경을 위한 아주 디테일한 최적화 설정입니다.
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1분
retry: 1,
},
},