외부 데이터 로딩
[!IMPORTANT] 이 가이드는 외부 상태 관리 라이브러리와, 데이터 가져오기, ssr, 하이드레이션/디하이드레이션 및 스트리밍을 위해 이러한 라이브러리를 TanStack Router와 통합하는 방법을 다룹니다. 표준 데이터 로딩 가이드를 아직 읽지 않았다면 먼저 읽습니다.
저장할까요, 조정할까요?
Router는 기본적으로 대부분의 데이터 요구 사항을 저장하고 관리할 수 있지만, 때로는 더 강력한 기능이 필요할 수 있습니다.
Router는 외부 데이터 가져오기 및 캐싱 라이브러리를 위한 완벽한 조정자로 설계되었습니다. 따라서 원하는 데이터 가져오기/캐싱 라이브러리를 사용할 수 있으며, 라우터는 사용자의 탐색과 최신 데이터에 대한 기대에 맞춰 데이터를 로드하도록 조정합니다.
어떤 데이터 가져오기 라이브러리를 지원하나요?
비동기 프로미스를 지원하는 모든 데이터 가져오기 라이브러리를 TanStack Router와 함께 사용할 수 있습니다. 예시는 다음과 같습니다.
또한 다음과 같은 라이브러리도 사용할 수 있습니다.
말 그대로 프로미스를 반환하고 데이터를 읽거나 쓸 수 있는 모든 라이브러리를 통합할 수 있습니다.
로더를 사용해 데이터가 로드되었는지 확인하기
외부 캐싱/데이터 라이브러리를 Router에 통합하는 가장 쉬운 방법은 route.loader를 사용해 라우트에 필요한 데이터가 로드되어 표시할 준비가 되었는지 확인하는 것입니다.
⚠️ 왜 그래야 하나요? 다음과 같은 이유로 렌더링에 중요한 데이터를 로더에서 프리로드하는 것이 매우 중요합니다.
- "로딩 표시가 깜박이는" 상태가 발생하지 않습니다.
- 컴포넌트 기반 가져오기로 인한 워터폴 데이터 가져오기가 발생하지 않습니다.
- SEO에 더 유리합니다. 렌더링 시점에 데이터를 사용할 수 있으면 검색 엔진이 색인할 수 있습니다.
다음은 Route의 loader 옵션으로 일부 데이터의 캐시를 시드하는 단순한 예시입니다(이렇게 사용하지 않습니다).
// src/routes/posts.tsx
let postsCache = []
export const Route = createFileRoute('/posts')({
loader: async () => {
postsCache = await fetchPosts()
},
component: () => {
return (
<div>
{postsCache.map((post) => (
<Post key={post.id} post={post} />
))}
</div>
)
},
})
이 예제에는 명백한 결함이 있지만, 라우트의 loader 옵션으로 캐시에 데이터를 시드할 수 있다는 점을 보여줍니다. 이제 TanStack Query를 사용하는 더 현실적인 예제를 살펴보겠습니다.
fetchPosts를 선호하는 데이터 가져오기 라이브러리의 프리페치 API로 바꿉니다.postsCache를 선호하는 데이터 가져오기 라이브러리의 읽기 또는 가져오기 API나 훅으로 바꿉니다.
TanStack Query를 사용하는 더 현실적인 예제
TanStack Query를 사용하는 더 현실적인 예제를 살펴보겠습니다.
// src/routes/posts.tsx
const postsQueryOptions = queryOptions({
queryKey: ['posts'],
queryFn: () => fetchPosts(),
})
export const Route = createFileRoute('/posts')({
// Use the `loader` option to ensure that the data is loaded
loader: () => queryClient.ensureQueryData(postsQueryOptions),
component: () => {
// Read the data from the cache and subscribe to updates
const {
data: { posts },
} = useSuspenseQuery(postsQueryOptions)
return (
<div>
{posts.map((post) => (
<Post key={post.id} post={post} />
))}
</div>
)
},
})
TanStack Query를 사용한 오류 처리
TanStack Query와 함께 suspense를 사용하는 동안 오류가 발생하면 다시 렌더링할 때 다시 시도하도록 쿼리에 알려야 합니다. useQueryErrorResetBoundary 훅이 제공하는 reset 함수를 사용하면 됩니다. 오류 컴포넌트가 마운트되는 즉시 이 함수를 이펙트에서 호출할 수 있습니다. 그러면 쿼리가 재설정되고 라우트 컴포넌트를 다시 렌더링할 때 데이터를 다시 가져옵니다. 사용자가 retry 버튼을 클릭하는 대신 라우트에서 벗어나는 경우도 처리합니다.
export const Route = createFileRoute('/')({
loader: () => queryClient.ensureQueryData(postsQueryOptions),
errorComponent: ({ error, reset }) => {
const router = useRouter()
const queryErrorResetBoundary = useQueryErrorResetBoundary()
useEffect(() => {
// Reset the query error boundary
queryErrorResetBoundary.reset()
}, [queryErrorResetBoundary])
return (
<div>
{error.message}
<button
onClick={() => {
// Invalidate the route to reload the loader, and reset any router error boundaries
router.invalidate()
}}
>
retry
</button>
</div>
)
},
})
SSR 디하이드레이션/하이드레이션
가능한 도구는 TanStack Router의 편리한 디하이드레이션/하이드레이션 API를 통합해 서버와 클라이언트 사이에서 디하이드레이트된 데이터를 전달하고 필요한 곳에서 다시 하이드레이트할 수 있습니다. 서드 파티의 중요 데이터와 서드 파티의 지연 데이터에 이를 적용하는 방법을 살펴보겠습니다.
중요 데이터의 디하이드레이션/하이드레이션
첫 번째 렌더링/페인트에 필요한 중요 데이터의 경우, TanStack Router는 Router를 구성할 때 dehydrate 및 hydrate 옵션을 지원합니다. 이러한 콜백은 라우터가 정상적으로 디하이드레이트 및 하이드레이트될 때 서버와 클라이언트에서 자동으로 호출되는 함수이며, 자체 데이터로 디하이드레이트된 데이터를 확장할 수 있습니다.
dehydrate 함수는 직렬화 가능한 모든 JSON 데이터를 반환할 수 있으며, 이 데이터는 클라이언트로 전송되는 디하이드레이트된 페이로드에 병합되어 삽입됩니다.
예를 들어 서버에서 가져온 데이터를 클라이언트에서 하이드레이션에 사용할 수 있도록 TanStack Query QueryClient를 디하이드레이트하고 하이드레이트해 보겠습니다.
// src/router.tsx
export function createRouter() {
// Make sure you create your loader client or similar data
// stores inside of your `createRouter` function. This ensures
// that your data stores are unique to each request and
// always present on both server and client.
const queryClient = new QueryClient()
return createRouter({
routeTree,
// Optionally provide your loaderClient to the router context for
// convenience (you can provide anything you want to the router
// context!)
context: {
queryClient,
},
// On the server, dehydrate the loader client so the router
// can serialize it and send it to the client for us
dehydrate: () => {
return {
queryClientState: dehydrate(queryClient),
}
},
// On the client, hydrate the loader client with the data
// we dehydrated on the server
hydrate: (dehydrated) => {
hydrate(queryClient, dehydrated.queryClientState)
},
// Optionally, we can use `Wrap` to wrap our router in the loader client provider
Wrap: ({ children }) => {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
)
},
})
}