지연 데이터 로딩
TanStack Router는 로더를 병렬로 실행하고 모든 로더가 해결될 때까지 기다린 후 다음 라우트를 렌더링하도록 설계되었습니다. 대부분의 경우에는 이것이 적합하지만, 때로는 나머지 데이터가 백그라운드에서 로드되는 동안 사용자에게 무언가를 더 빨리 보여주고 싶을 수 있습니다.
지연 데이터 로딩은 느린 비핵심 라우트 데이터가 백그라운드에서 해결되는 동안 라우터가 다음 location의 핵심 데이터/마크업을 렌더링하도록 하는 패턴입니다. 이 프로세스는 클라이언트와 서버 모두에서 작동하며(스트리밍 사용), 애플리케이션의 체감 성능을 높이는 좋은 방법입니다.
TanStack Query와 같은 라이브러리나 다른 데이터 가져오기 라이브러리를 사용하는 경우 지연 데이터 로딩이 조금 다르게 작동합니다. 자세한 내용은 외부 라이브러리를 사용한 지연 데이터 로딩 섹션으로 건너뜁니다.
Await를 사용한 지연 데이터 로딩
느리거나 비핵심인 데이터를 지연하려면 로더 응답의 어느 위치에서든 await하지 않은/해결되지 않은 promise를 반환합니다.
// src/routes/posts.$postId.tsx
import { createFileRoute, defer } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async () => {
// Fetch some slower data, but do not await it
const slowDataPromise = fetchSlowData()
// Fetch and await some data that resolves quickly
const fastData = await fetchFastData()
return {
fastData,
deferredSlowData: slowDataPromise,
}
},
})
await한 promise가 하나라도 해결되면 지연된 promise가 계속 해결되는 동안 다음 라우트가 렌더링을 시작합니다.
컴포넌트에서는 Await 컴포넌트를 사용해 지연된 promise를 해결하고 활용할 수 있습니다.
// src/routes/posts.$postId.tsx
import { createFileRoute, Await } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
// ...
component: PostIdComponent,
})
function PostIdComponent() {
const { deferredSlowData, fastData } = Route.useLoaderData()
// do something with fastData
return (
<Await promise={deferredSlowData} fallback={<div>Loading...</div>}>
{(data) => {
return <div>{data}</div>
}}
</Await>
)
}
[!TIP] 컴포넌트가 코드 분할되어 있다면 getRouteApi 함수를 사용해 타입이 지정된
useLoaderData()훅에 액세스하기 위해Route구성을 import하지 않아도 됩니다.
Await 컴포넌트는 promise가 해결될 때까지 가장 가까운 suspense 경계를 트리거해 promise를 해결한 후, 해결된 데이터와 함께 컴포넌트의 children을 함수로 렌더링합니다.
promise가 거부되면 Await 컴포넌트가 직렬화된 오류를 throw하며, 가장 가까운 오류 경계에서 이를 잡을 수 있습니다.
React
[!TIP] React 19에서는
Await대신use()훅을 사용할 수 있습니다.
외부 라이브러리를 사용한 지연 데이터 로딩
라우트 정보를 가져오는 전략이 TanStack Query와 같은 외부 라이브러리를 사용한 외부 데이터 로딩에 의존하는 경우, TanStack Router 외부에서 라이브러리가 데이터 가져오기와 캐싱을 대신 처리하므로 지연 데이터 로딩이 조금 다르게 작동합니다.
따라서 defer와 Await를 사용하는 대신 Route의 loader를 사용해 데이터 가져오기를 시작한 다음 라이브러리의 훅을 사용해 컴포넌트에서 데이터에 액세스합니다.
React
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
import { slowDataOptions, fastDataOptions } from '~/api/query-options'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ context: { queryClient } }) => {
// Kick off the fetching of some slower data, but do not await it
queryClient.prefetchQuery(slowDataOptions())
// Fetch and await some data that resolves quickly
await queryClient.ensureQueryData(fastDataOptions())
},
})
Solid
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { slowDataOptions, fastDataOptions } from '~/api/query-options'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ context: { queryClient } }) => {
// Kick off the fetching of some slower data, but do not await it
queryClient.prefetchQuery(slowDataOptions())
// Fetch and await some data that resolves quickly
await queryClient.ensureQueryData(fastDataOptions())
},
})
그런 다음 컴포넌트에서 라이브러리의 훅을 사용해 데이터에 액세스할 수 있습니다.
React
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
import { useSuspenseQuery } from '@tanstack/react-query'
import { slowDataOptions, fastDataOptions } from '~/api/query-options'
export const Route = createFileRoute('/posts/$postId')({
// ...
component: PostIdComponent,
})
function PostIdComponent() {
const fastData = useSuspenseQuery(fastDataOptions())
// do something with fastData
return (
<Suspense fallback={<div>Loading...</div>}>
<SlowDataComponent />
</Suspense>
)
}
function SlowDataComponent() {
const data = useSuspenseQuery(slowDataOptions())
return <div>{data}</div>
}
Solid
// src/routes/posts.$postId.tsx
import { createFileRoute } from '@tanstack/solid-router'
import { useSuspenseQuery } from '@tanstack/solid-query'
import { slowDataOptions, fastDataOptions } from '~/api/query-options'
export const Route = createFileRoute('/posts/$postId')({
// ...
component: PostIdComponent,
})
function PostIdComponent() {
const fastData = useSuspenseQuery(fastDataOptions())
// do something with fastData
return (
<Suspense fallback={<div>Loading...</div>}>
<SlowDataComponent />
</Suspense>
)
}
function SlowDataComponent() {
const data = useSuspenseQuery(slowDataOptions())
return <div>{data()}</div>
}
캐싱 및 무효화
스트리밍된 promise는 연결된 loader 데이터와 동일한 생명 주기를 따릅니다. 프리로드할 수도 있습니다!
React
SSR 및 스트리밍 지연 데이터
스트리밍을 사용하려면 이를 지원하는 서버와 이를 올바르게 사용하도록 구성된 TanStack Router가 필요합니다.
서버를 스트리밍에 맞게 설정하는 단계별 지침은 스트리밍 SSR 가이드 전체를 읽습니다.
SSR 스트리밍 생명 주기
다음은 TanStack Router에서 지연 데이터 스트리밍이 작동하는 방식을 간략하게 설명한 내용입니다.
- 서버
- Promise가 라우트 로더에서 반환될 때 표시되고 추적됩니다.
- 모든 로더가 해결되고 지연된 promise가 직렬화되어 HTML에 삽입됩니다.
- 라우트가 렌더링을 시작합니다.
<Await>컴포넌트로 렌더링된 지연된 promise가 suspense 경계를 트리거하므로 서버가 해당 지점까지 HTML을 스트리밍할 수 있습니다.
- 클라이언트
- 클라이언트가 서버에서 초기 HTML을 받습니다.
<Await>컴포넌트는 서버에서 데이터가 해결되기를 기다리는 동안 placeholder promise와 함께 일시 중단됩니다.
- 서버
- 지연된 promise가 해결되면 그 결과(또는 오류)가 직렬화되어 인라인 script 태그를 통해 클라이언트로 스트리밍됩니다.
- 해결된
<Await>컴포넌트와 해당 suspense 경계가 해결되고, 그 결과 HTML이 디하이드레이션된 데이터와 함께 클라이언트로 스트리밍됩니다.
- 클라이언트
<Await>내부에서 일시 중단된 placeholder promise가 스트리밍된 데이터/오류 응답으로 해결되고, 결과를 렌더링하거나 가장 가까운 오류 경계로 오류를 throw합니다.