TanStack Query

CSR vs SSR Provider 설정 가이드

핵심 개념

💡왜 설정이 다른가요?

서버는 여러 사용자의 요청을 동시에 처리합니다. 하나의 QueryClient를 공유하면 사용자 A의 데이터가 사용자 B에게 노출될 수 있습니다.

클라이언트는 한 사용자만 사용하므로 QueryClient를 재사용해도 안전합니다. 오히려 재사용해야 캐시가 유지됩니다.

3가지 데이터 페칭 전략

  • CSR: 클라이언트에서 useQuery로 fetch
  • SSR + Hydration: 서버에서 prefetch → 클라이언트로 전달
  • Streaming: Suspense로 점진적 렌더링

기존 코드의 문제점

문제가 있는 코드
1// ❌ 기존 코드의 문제점
2 
3// 문제 1: 모듈 레벨에서 호출
4export const queryClient = getQueryClient();
5// → 서버에서 이 모듈이 import될 때마다 실행됨
6 
7// 문제 2: Provider props에서 직접 호출
8<QueryClientProvider client={getQueryClient()}>
9// → 매 렌더링마다 호출될 수 있음

왜 문제인가요?

  • 모듈 레벨 호출: 서버에서 모듈이 캐시되면 모든 요청이 같은 QueryClient를 공유할 수 있음
  • Props에서 직접 호출: React 렌더링 사이클에서 예측하기 어려운 동작

올바른 패턴

권장 패턴 (Next.js App Router)
1// ✅ 올바른 패턴 (Next.js App Router)
2 
3'use client';
4import { useState } from 'react';
5import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
6 
7function makeQueryClient() {
8 return new QueryClient({ /* 옵션 */ });
9}
10 
11let browserQueryClient: QueryClient | undefined;
12 
13function getQueryClient() {
14 if (typeof window === 'undefined') {
15 // 서버: 항상 새로 생성 (요청 격리)
16 return makeQueryClient();
17 }
18 // 클라이언트: 싱글톤
19 if (!browserQueryClient) {
20 browserQueryClient = makeQueryClient();
21 }
22 return browserQueryClient;
23}
24 
25export default function QueryProvider({ children }) {
26 // useState 초기화 함수로 전달 → 마운트 시 1회만 실행
27 const [queryClient] = useState(getQueryClient);
28 
29 return (
30 <QueryClientProvider client={queryClient}>
31 {children}
32 </QueryClientProvider>
33 );
34}

핵심 포인트

  • typeof window === 'undefined'로 서버/클라이언트 구분
  • useState(getQueryClient)로 마운트 시 1회만 실행
  • 서버에서는 매번 새 인스턴스, 클라이언트에서는 싱글톤 유지

인터랙티브 데모

각 방식의 차이를 직접 체험해보세요. 네트워크 탭을 열어 데이터 로딩 타이밍을 확인할 수 있습니다.

방식 비교

특성CSRSSR + HydrationStreaming
초기 HTML빈 상태 / 스켈레톤완성된 데이터스켈레톤 → 데이터
TTFB빠름느림 (데이터 대기)빠름
SEO불리유리유리
사용 훅useQueryuseQuery + prefetchuseSuspenseQuery
적합한 경우대시보드, 인증 필요 페이지SEO 중요, 정적 콘텐츠복잡한 페이지, 병렬 데이터