핵심 요약
TanStack Query v6는 React Server Components와의 통합이 1순위 변경. 서버에서 prefetch한 데이터를 클라이언트가 그대로 이어받는 패턴이 표준화됐다. 또한 streaming 응답을 query로 다루는 streamedQuery API가 추가됐다.
- v6 출시: 2026-02
- 핵심 API: HydrationBoundary, useSuspenseQuery, streamedQuery
- 호환: React 19, Next.js 15+, Remix v3
1. QueryClient — 요청별 인스턴스
// app/providers.tsx
'use client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
export function Providers({ children }: { children: React.ReactNode }) {
const [client] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
gcTime: 5 * 60 * 1000,
},
},
}))
return <QueryClientProvider client={client}>{children}</QueryClientProvider>
}모듈 레벨 singleton 절대 금지. SSR에서 다른 사용자 데이터 섞임.
2. 서버 prefetch + dehydrate
// app/posts/page.tsx (RSC)
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { Posts } from './Posts'
export default async function PostsPage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: () => fetch('https://api/posts').then(r => r.json()),
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Posts />
</HydrationBoundary>
)
}3. 클라이언트 useSuspenseQuery
// app/posts/Posts.tsx
'use client'
import { useSuspenseQuery } from '@tanstack/react-query'
import { Suspense } from 'react'
function PostsList() {
const { data } = useSuspenseQuery({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then(r => r.json()),
})
return <ul>{data.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}
export function Posts() {
return (
<Suspense fallback={<Loading />}>
<PostsList />
</Suspense>
)
}4. v6 신기능 — streamedQuery
SSE·streaming 응답을 query처럼 다룸. 부분 업데이트.
import { streamedQuery } from '@tanstack/react-query'
const { data } = useQuery({
queryKey: ['ai-response', prompt],
queryFn: streamedQuery({
queryFn: async ({ signal }) => {
const res = await fetch('/api/llm', {
method: 'POST',
body: JSON.stringify({ prompt }),
signal,
})
return res.body // ReadableStream
},
refetchMode: 'reset', // 또는 'append'
}),
})
// data가 토큰별로 부분 업데이트됨LLM·SSE 응답을 query 캐시 시스템 안에서 자연스럽게 다루는 핵심 기능.
5. Mutation 패턴
function CreatePost() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: (newPost) => fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(newPost),
}).then(r => r.json()),
// optimistic update
onMutate: async (newPost) => {
await queryClient.cancelQueries({ queryKey: ['posts'] })
const previous = queryClient.getQueryData(['posts'])
queryClient.setQueryData(['posts'], (old) => [...old, { ...newPost, id: Date.now() }])
return { previous }
},
onError: (err, newPost, context) => {
queryClient.setQueryData(['posts'], context.previous)
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})
}6. Infinite Query — 무한 스크롤
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['posts'],
queryFn: ({ pageParam = 0 }) => fetch(`/api/posts?cursor=${pageParam}`).then(r => r.json()),
getNextPageParam: (last) => last.nextCursor,
initialPageParam: 0,
})7. 흔한 함정 5가지
1) staleTime 너무 짧음
기본 0이면 hydrate 직후 즉시 stale 판정 → 클라이언트가 재 fetch. staleTime: 60s 이상 권장.
2) queryFn URL이 환경별로 달라야 함
const baseURL = typeof window === 'undefined'
? process.env.INTERNAL_API_URL
: ''
// 서버에선 절대 URL 필요3) Suspense 경계 누락
useSuspenseQuery는 반드시 Suspense 안에서.
4) hydration mismatch
Date·Math.random은 클라이언트에서만.
5) QueryClient singleton 오염
useState로 요청별 생성 — 모듈 레벨 절대 금지.
8. 도구
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
<QueryClientProvider client={client}>
{children}
{process.env.NODE_ENV === 'development' && <ReactQueryDevtools />}
</QueryClientProvider>devtools에서 Server/Client 캐시 상태 비교가 가장 빠른 디버깅.
9. 큰 그림 — TanStack Query 정말 필요한가?
RSC가 충분히 강력해 단순 fetch는 RSC만으로 끝. TanStack Query 도입 정당화 조건:
- infinite scroll·optimistic update 빈번
- 복잡한 캐시 무효화 정책
- WebSocket·SSE 실시간 데이터와 query 통합
단순 CRUD는 RSC + Server Actions로 충분.
자주 묻는 질문
v5 → v6 마이그레이션?codemod 제공. 90% 자동. 주요 변경은 streamedQuery 추가 + RSC 통합 정리.
SWR과 차이?
TanStack Query가 기능 풍부 (mutation·infinite·optimistic). SWR은 더 가벼움. RSC 통합은 둘 다 비슷.

댓글 0