createGetQueryClient
createGetQueryClient는 실험 기능이므로 인터페이스가 변경될 수 있습니다.
서버 환경에서 안전하게 QueryClient 인스턴스를 관리하는 유틸리티 함수입니다.
- 서버 환경: 각 요청마다 새로운 QueryClient 인스턴스를 생성
- 브라우저 환경: 싱글톤 QueryClient 인스턴스를 반환 (없으면 생성)
이 패턴은 Next.js와 같은 SSR 프레임워크에서 React Query를 올바르게 사용하기 위해 필수적입니다. 서버에서 요청 간 QueryClient 상태를 공유하지 않으면서, 브라우저에서는 리렌더링 간 캐시를 유지하기 위해 단일 인스턴스를 유지합니다.
서버에서 QueryClient를 요청 간 공유하면 심각한 보안 취약점이 발생할 수 있습니다. getQueryClient는 각 요청마다 새로운 QueryClient 인스턴스를 생성하여 사용자 간 데이터 누수와 민감한 정보 노출을 방지합니다. 자세한 내용은 아래 서버 환경 동작 섹션을 참고하세요.
기본 사용법
먼저 프로젝트에 get-query-client.ts 파일을 만듭니다:
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
export const { getQueryClient } = createGetQueryClient()그런 다음 코드에서 import하여 사용합니다:
// app/providers.tsx
'use client'
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
)
}설정
createGetQueryClient에 QueryClientConfig를 전달하여 QueryClient 인스턴스를 커스터마이징할 수 있습니다:
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
export const { getQueryClient } = createGetQueryClient({
defaultOptions: {
queries: {
staleTime: 5000,
retry: 3,
},
},
})// app/providers.tsx
'use client'
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
)
}브라우저 환경 동작
브라우저 환경에서는 createGetQueryClient 호출 시 전달한 config가 QueryClient 인스턴스를 생성하는 데 사용됩니다. getQueryClient 함수는 인자를 받지 않으며 항상 동일한 싱글톤 인스턴스를 반환합니다. 이는 리렌더링 간 안정적인 싱글톤을 유지하기 위한 의도적인 동작입니다.
서버 환경 동작
보안 문제 방지 (최우선)
서버에서 QueryClient를 요청 간 공유하면 심각한 보안 취약점이 발생할 수 있습니다:
- 사용자 간 데이터 누수: 한 사용자의 데이터가 다른 사용자에게 노출될 수 있습니다
- 민감한 정보 노출: 인증 토큰, 개인 정보 등이 다른 사용자의 요청에 포함될 수 있습니다
- 잘못된 캐시 상태: 이전 요청의 캐시 데이터가 다음 요청에 잘못 반환될 수 있습니다
getQueryClient는 각 요청마다 새로운 QueryClient 인스턴스를 생성함으로써 이러한 보안 문제를 완전히 방지합니다.
💡 관련 문서: 서버에서 QueryClient를 요청 간 공유하면 안 되는 이유에 대한 자세한 내용은 TanStack Query SSR 가이드 - Initial Setup 섹션을 참고하세요.
OOM 방지를 위한 캐시 제거 시간 설정
서버 환경에서 getQueryClient는 메모리 누수와 OOM(Out of Memory) 오류를 방지하기 위해 QueryClient 캐시에서 쿼리 데이터를 제거하는 시간을 Infinity로 설정합니다. 이는 서버가 많은 동시 요청을 처리할 때 발생할 수 있는 메모리 문제를 해결하기 위한 추가적인 안전 조치입니다.
gcTime(v5) 또는 cacheTime(v4)은 TanStack Query가 QueryClient의 캐시에서 쿼리 데이터를 제거하기까지의 시간을 의미합니다. 서버에서 이 값을 Infinity로 설정하면:
- 캐시에서 데이터가 자동으로 제거되지 않아 요청 완료 전까지 데이터가 유지됩니다
- TanStack Query 내부에서
isValidTimeout검사를 통해scheduleGc에서setTimeout스케줄링이 방지되어, 많은 동시 요청 시 발생할 수 있는setTimeout으로 인한 성능 저하를 완전히 제거합니다
이 동작은 자동이며 재정의할 수 없습니다 - config에서 캐시 제거 시간을 다른 값으로 제공하더라도, 서버에서는 Infinity로 설정됩니다. (v4에서는 cacheTime, v5에서는 gcTime 옵션을 사용합니다. 버전별 차이는 아래 섹션을 참고하세요.)
💡 관련 문서: 서버에서의 높은 메모리 소비와 서버에서
gcTime이 기본적으로Infinity로 설정되는 이유에 대한 자세한 내용은 TanStack Query SSR 가이드 - 서버에서의 높은 메모리 소비 섹션을 참고하세요.
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
// Config는 getQueryClient가 아닌 createGetQueryClient에 전달됩니다
export const { getQueryClient } = createGetQueryClient({
defaultOptions: {
queries: {
// 서버에서는 캐시 제거 시간이 Infinity로 설정되어
// 요청 완료 전까지 캐시가 유지되고 setTimeout 스케줄링이 방지됩니다
gcTime: 5000, // v5: 서버에서는 Infinity로 재정의됩니다
// v4에서는 cacheTime이 사용되며, 마찬가지로 Infinity로 재정의됩니다
},
},
})
// 서버: 캐시 제거 시간은 항상 Infinity (OOM 방지)
// 브라우저: 제공된 값(또는 기본값)을 사용요청마다 새로운 인스턴스
서버에서 getQueryClient는 각 호출마다 새로운 QueryClient 인스턴스를 생성합니다. 이를 통해:
- 각 요청이 격리된 캐시 상태를 가집니다
- 다른 사용자 요청 간 데이터 누수가 없습니다
- 요청 완료 시 적절한 정리가 수행됩니다
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
export const { getQueryClient } = createGetQueryClient()// 서버 환경
const queryClient1 = getQueryClient()
const queryClient2 = getQueryClient()
// queryClient1 !== queryClient2 (다른 인스턴스)브라우저 환경 동작
싱글톤 패턴
브라우저에서 getQueryClient는 여러 호출에 걸쳐 동일한 QueryClient 인스턴스를 반환합니다. 이를 통해:
- 리렌더링 간 캐시가 유지됩니다
- 일관된 상태 관리가 가능합니다
- 최적의 성능을 제공합니다
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
export const { getQueryClient } = createGetQueryClient()// 브라우저 환경
const queryClient1 = getQueryClient()
const queryClient2 = getQueryClient()
// queryClient1 === queryClient2 (동일한 인스턴스)사용 사례
Next.js App Router
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
export const { getQueryClient } = createGetQueryClient()// app/providers.tsx
'use client'
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
)
}// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}커스텀 설정과 함께 사용
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
export const { getQueryClient } = createGetQueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5분
gcTime: 1000 * 60 * 10, // 10분 (v5)
// cacheTime: 1000 * 60 * 10, // 10분 (v4)
retry: 2,
refetchOnWindowFocus: false,
},
mutations: {
retry: 1,
},
},
})// app/providers.tsx
'use client'
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
)
}버전 차이
@tanstack/react-query v4
v4에서는 createGetQueryClient 함수를 사용하며, 캐시 시간 옵션이 cacheTime이라고 불립니다:
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
export const { getQueryClient } = createGetQueryClient()// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
// 서버: cacheTime은 자동으로 Infinity로 설정됩니다
export const { getQueryClient } = createGetQueryClient({
defaultOptions: {
queries: {
cacheTime: 5000, // 서버에서는 Infinity로 재정의됩니다
},
},
})// app/providers.tsx
'use client'
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
)
}@tanstack/react-query v5
v5에서는 createGetQueryClient 함수를 사용하며, 캐시 시간 옵션이 gcTime (garbage collection time)이라고 불립니다:
// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
export const { getQueryClient } = createGetQueryClient()// get-query-client.ts
import { createGetQueryClient } from '@suspensive/react-query'
// 서버: gcTime은 자동으로 Infinity로 설정됩니다
export const { getQueryClient } = createGetQueryClient({
defaultOptions: {
queries: {
gcTime: 5000, // 서버에서는 Infinity로 재정의됩니다
},
},
})// app/providers.tsx
'use client'
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { getQueryClient } from '@/app/get-query-client'
import type * as React from 'react'
export default function Providers({ children }: { children: React.ReactNode }) {
const queryClient = getQueryClient()
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools />
</QueryClientProvider>
)
}중요 사항
- 서버 안전성: SSR 환경에서
new QueryClient()를 직접 생성하는 대신 항상getQueryClient를 사용하여 적절한 요청 격리와 OOM 방지를 보장하세요.
전역 변수로 사용하지 마세요
getQueryClient를 사용하더라도 App Router에서 export const queryClient = getQueryClient()처럼 전역 변수에 저장하고 다른 곳에서 import해서 사용하면 동일한 보안 위험이 발생합니다. 서버에서 QueryClient 인스턴스가 요청 간 공유되어 사용자 간 데이터 누수가 발생할 수 있습니다. 전역 변수에 저장하지 말고 필요한 곳에서 직접 getQueryClient()를 호출하세요.
-
브라우저 싱글톤: 브라우저 인스턴스는 싱글톤입니다.
getQueryClient는 인자를 받지 않으므로createGetQueryClient를 호출할 때 QueryClient를 구성하세요. -
자동 OOM 방지: 서버에서 캐시 제거 시간을
Infinity로 재정의하는 것은 자동이며 비활성화할 수 없습니다. 이는 메모리 문제를 방지하기 위한 안전 기능입니다. (v4에서는cacheTime, v5에서는gcTime옵션을 사용합니다.)