본문으로 건너뛰기

TanStack Query 통합

[!IMPORTANT] 이 통합은 TanStack Router와 TanStack Query 간 SSR 디하이드레이션/하이드레이션 및 스트리밍을 자동화합니다. 표준 외부 데이터 로딩 가이드를 읽지 않았다면 먼저 해당 가이드부터 확인합니다.

제공 기능

  • QueryClient자동 SSR 디하이드레이션/하이드레이션
  • 초기 서버 렌더링 중 해결되는 쿼리를 클라이언트로 스트리밍
  • 쿼리/뮤테이션에서 throw된 redirect()리디렉션 처리
  • QueryClientProvider를 사용한 선택적 프로바이더 래핑

설치

TanStack Query 통합은 별도로 설치해야 하는 패키지입니다.

react: @tanstack/react-router-ssr-query solid: @tanstack/solid-router-ssr-query

설정

라우터를 만들고 통합을 연결합니다. SSR 환경에서는 요청마다 새로운 QueryClient가 생성되는지 확인합니다.

React

src/router.tsx
import { QueryClient } from '@tanstack/react-query'
import { createRouter } from '@tanstack/react-router'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
import { routeTree } from './routeTree.gen'

export function getRouter() {
const queryClient = new QueryClient()
const router = createRouter({
routeTree,
// optionally expose the QueryClient via router context
context: { queryClient },
scrollRestoration: true,
defaultPreload: 'intent',
})

setupRouterSsrQueryIntegration({
router,
queryClient,
// optional:
// handleRedirects: true,
// wrapQueryClient: true,
})

return router
}

기본적으로 통합은 라우터를 QueryClientProvider로 래핑합니다. 이미 자체 프로바이더를 제공한다면 wrapQueryClient: false를 전달하고 사용자 지정 래퍼를 유지합니다.

dehydrateOptionshydrateOptions를 전달해 TanStack Query가 SSR 페이로드를 직렬화하고 클라이언트에서 복원하는 방식을 사용자 지정할 수도 있습니다.

setupRouterSsrQueryIntegration({
router,
queryClient,
dehydrateOptions: {
shouldDehydrateQuery: (query) => query.meta?.ssr !== false,
},
hydrateOptions: {
defaultOptions: {
queries: {
gcTime: 60_000,
},
},
},
})

이러한 옵션의 일반적인 사용 방법은 다음과 같습니다.

  • hydrateOptions.defaultOptions.queries.gcTime을 설정해 하이드레이션된 SSR 쿼리가 가비지 컬렉션 전에 클라이언트 캐시에 유지되는 시간을 제어합니다.
  • dehydrateOptions.shouldDehydrateQuery를 설정해 HTML 페이로드로 직렬화하지 않을 쿼리를 제외합니다.
  • SSR 페이로드에 사용자 지정 직렬화가 필요하면 dehydrateOptions.serializeDatahydrateOptions.defaultOptions.deserializeData를 설정합니다.
src/router.tsx
import { QueryClient } from '@tanstack/react-query'
import { createRouter } from '@tanstack/react-router'
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
import { routeTree } from './routeTree.gen'

export function getRouter() {
const queryClient = new QueryClient()

const router = createRouter({
routeTree,
context: { queryClient },
})

setupRouterSsrQueryIntegration({
router,
queryClient,
hydrateOptions: {
defaultOptions: {
queries: {
gcTime: 5 * 60 * 1000,
},
},
},
})

return router
}

SSR 동작 및 스트리밍

  • 서버 렌더링 중 통합은 초기 쿼리를 디하이드레이션하고 렌더링 중 해결되는 후속 쿼리를 스트리밍합니다.
  • 클라이언트에서 통합은 초기 상태를 하이드레이션한 다음 스트리밍된 쿼리를 점진적으로 하이드레이션합니다.
  • useSuspenseQuery의 쿼리 또는 로더 프리페치는 SSR/스트리밍에 참여합니다. 일반 useQuery는 서버에서 실행되지 않습니다.

라우트에서 사용

useSuspenseQuery와 useQuery 비교

  • useSuspenseQuery: 데이터가 필요할 때 SSR 중 서버에서 실행되며, 데이터가 해결되는 대로 클라이언트로 스트리밍됩니다.
  • useQuery: 서버에서 실행되지 않고 하이드레이션 후 클라이언트에서 가져옵니다. SSR에 필요하지 않은 데이터에 사용합니다.

React

// Suspense: executes on server and streams
const { data } = useSuspenseQuery(postsQuery)

// Non-suspense: executes only on client
const { data, isLoading } = useQuery(postsQuery)

로더로 프리로드하고 훅으로 읽기

워터폴과 로딩 깜박임을 피하려면 라우트 loader에서 중요한 데이터를 프리로드한 다음 컴포넌트에서 읽습니다. 통합은 서버에서 가져온 데이터가 SSR 중 디하이드레이션되어 클라이언트로 스트리밍되도록 합니다.

React

src/routes/posts.tsx
import { queryOptions, useSuspenseQuery, useQuery } from '@tanstack/react-query'
import { createFileRoute } from '@tanstack/react-router'

const postsQuery = queryOptions({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then((r) => r.json()),
})

export const Route = createFileRoute('/posts')({
// Ensure the data is in the cache before render
loader: ({ context }) => context.queryClient.ensureQueryData(postsQuery),
component: PostsPage,
})

function PostsPage() {
// Prefer suspense for best SSR + streaming behavior
const { data } = useSuspenseQuery(postsQuery)
return <div>{data.map((p: any) => p.title).join(', ')}</div>
}

프리페치 및 스트리밍

컴포넌트에서 데이터를 사용하지 않고도 로더에서 fetchQuery 또는 ensureQueryData로 프리페치할 수 있습니다. 로더에서 프로미스를 직접 반환하면 이를 await하므로 쿼리가 완료될 때까지 SSR 요청이 차단됩니다. 프로미스를 await하지도 반환하지도 않으면 서버에서 쿼리를 시작하고 SSR 요청을 차단하지 않은 채 클라이언트로 스트리밍합니다.

React

src/routes/users.$id.tsx
import { createFileRoute } from '@tanstack/react-router'
import { queryOptions, useQuery } from '@tanstack/react-query'

const userQuery = (id: string) =>
queryOptions({
queryKey: ['user', id],
queryFn: () => fetch(`/api/users/${id}`).then((r) => r.json()),
})

export const Route = createFileRoute('/user/$id')({
loader: ({ params }) => {
// do not await this nor return the promise, just kick off the query to stream it to the client
context.queryClient.fetchQuery(userQuery(params.id))
},
})

리디렉션 처리

쿼리 또는 뮤테이션이 redirect(...)를 throw하면 통합이 클라이언트에서 이를 가로채 라우터 내비게이션을 수행합니다.

  • 기본적으로 활성화됩니다.
  • 사용자 지정 처리가 필요하면 handleRedirects: false로 비활성화합니다.

TanStack Start와 함께 사용

TanStack Start는 내부적으로 TanStack Router를 사용합니다. 동일한 설정을 적용하며, 통합이 SSR 중 쿼리 결과를 자동으로 스트리밍합니다.