본문으로 건너뛰기

프리로딩

TanStack Router의 프리로딩은 사용자가 실제로 이동하기 전에 라우트를 로드하는 방법입니다. 다음에 방문할 가능성이 높은 라우트에 유용합니다. 예를 들어 게시물 목록이 있고 사용자가 그중 하나를 클릭할 가능성이 높다면 게시물 라우트를 프리로드하여 클릭하는 즉시 사용할 수 있도록 준비할 수 있습니다.

지원되는 프리로딩 전략

  • 의도
    • "intent" 프리로딩은 <Link> 컴포넌트의 hover 및 touch start 이벤트를 사용하여 대상 라우트의 종속성을 프리로드합니다.
    • 사용자가 다음에 방문할 가능성이 높은 라우트를 프리로드할 때 유용한 전략입니다.
  • 뷰포트 가시성
    • "viewport" 프리로딩은 Intersection Observer API를 사용하여 <Link> 컴포넌트가 뷰포트에 있을 때 대상 라우트의 종속성을 프리로드합니다.
    • 접힌 부분 아래나 화면 밖에 있는 라우트를 프리로드할 때 유용한 전략입니다.
  • 렌더링
    • "render" 프리로딩은 <Link> 컴포넌트가 DOM에 렌더링되는 즉시 대상 라우트의 종속성을 프리로드합니다.
    • 항상 필요한 라우트를 프리로드할 때 유용한 전략입니다.

프리로드된 데이터는 메모리에 얼마나 오래 유지되나요?

성공한 프리로드 로더 결과는 서로 독립적인 다음 두 정책에 따라 라우터의 인메모리 캐시에 들어갈 수 있습니다.

  • 신선도 기본값은 30초입니다. defaultPreloadStaleTime 또는 라우트의 preloadStaleTime으로 구성합니다.
  • 미사용 보존 기간의 기본값은 5분입니다. defaultPreloadGcTime 또는 라우트의 preloadGcTime으로 구성합니다. 더 오래된 미사용 항목은 이후 캐시 조정 중 정리 대상이 될 수 있습니다.
  • 추측성 레인은 라우터 상태로 승격되지 않습니다. 탐색은 자체 프레젠테이션을 만들고 자체 beforeLoad 체인을 실행합니다. 캐시된 로더 데이터를 재사용하거나 아직 진행 중인 로더에 합류할 수 있습니다.

프리로딩, 캐싱 또는 프리로드된 데이터의 가비지 컬렉션을 더 세밀하게 제어해야 한다면 TanStack Query 같은 외부 캐싱 라이브러리를 사용합니다.

애플리케이션의 라우트를 프리로드하는 가장 간단한 방법은 전체 라우터의 defaultPreload 옵션을 intent로 설정하는 것입니다.

React

import { createRouter } from '@tanstack/react-router'

const router = createRouter({
// ...
defaultPreload: 'intent',
})

Solid

import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
// ...
defaultPreload: 'intent',
})

이렇게 하면 애플리케이션의 모든 intent 프리로딩 대상 <Link> 컴포넌트에서 기본적으로 활성화됩니다. 개별 preload prop을 <Link> 컴포넌트에 설정하여 기본 동작을 재정의할 수도 있습니다.

프리로드 지연 시간

기본적으로 intent focus/hover 및 뷰포트 프리로딩은 50ms 후에 시작됩니다. 포커스나 hover가 끝나거나 링크가 뷰포트를 벗어나면 대기 중인 프리로드가 취소됩니다. touch intent 프리로딩은 즉시 시작됩니다. 라우터의 defaultPreloadDelay 옵션을 설정하여 이 지연 시간을 변경할 수 있습니다.

React

import { createRouter } from '@tanstack/react-router'

const router = createRouter({
// ...
defaultPreloadDelay: 100,
})

Solid

import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
// ...
defaultPreloadDelay: 100,
})

개별 preloadDelay prop을 <Link> 컴포넌트에 설정하여 링크별로 기본 동작을 재정의할 수도 있습니다.

기본 제공 프리로딩, 신선도 및 보존

기본 제공 로더를 사용하는 경우 routerOptions.defaultPreloadStaleTime 또는 routeOptions.preloadStaleTime을 밀리초 단위 숫자로 설정하여 프리로드된 데이터가 신선한 것으로 간주되는 기간을 제어할 수 있습니다. 기본적으로 프리로드된 데이터는 30초 동안 신선한 것으로 간주됩니다.

신선도와 보존은 별개입니다. preloadStaleTime은 다른 로더 호출 없이 보존된 로더 결과를 재사용할 수 있는지 제어합니다. preloadGcTime(또는 defaultPreloadGcTime)은 미사용 프리로드 결과가 이후 캐시 조정 중 정리 대상이 되는 시점을 제어하며, 정확히 그 시점에 결과를 제거하도록 타이머를 예약하지는 않습니다. 두 프리로드 GC 옵션의 기본값은 5분입니다.

이를 변경하려면 라우터에서 defaultPreloadStaleTime 옵션을 설정합니다.

React

import { createRouter } from '@tanstack/react-router'

const router = createRouter({
// ...
defaultPreloadStaleTime: 10_000,
})

Solid

import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
// ...
defaultPreloadStaleTime: 10_000,
})

또는 개별 라우트에서 routeOptions.preloadStaleTime 옵션을 사용할 수 있습니다.

// src/routes/posts.$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => fetchPost(params.postId),
// Reload preloaded data when it is more than 10 seconds old
preloadStaleTime: 10_000,
})

클라이언트 측 프리로딩은 각 라우트의 beforeLoadpreload: true로 실행합니다. 이후의 모든 프리로드 또는 탐색은 자체 beforeLoad 체인을 실행하므로 동일한 프리로드가 여전히 활성 상태여도 탐색에서는 preload: false를 확인합니다. 이후 레인은 성공적으로 완료된 로더 데이터를 재사용하거나 아직 진행 중인 로더 작업에 합류할 수 있지만 beforeLoad 컨텍스트나 이미 완료된 리디렉션, 오류, not-found 결과를 재사용하지는 않습니다. 합류한 로더 작업이 나중에 최종 결과를 생성하면 해당 로더 작업의 모든 현재 소비자가 이를 확인합니다. shouldReload 옵션은 로더에만 적용됩니다.

라우트에 preload: false가 있으면 추측성 레인은 여전히 beforeLoad를 실행하지만 해당 라우트의 로더는 건너뜁니다. 탐색은 beforeLoad를 다시 실행하고 건너뛴 로더 작업을 수행합니다.

외부 라이브러리를 사용한 프리로딩

stale 데이터를 판별하는 자체 메커니즘이 있는 React Query 같은 외부 캐싱 라이브러리를 통합할 때는 TanStack Router의 기본 프리로딩 및 stale-while-revalidate 로직을 재정의하고 싶을 수 있습니다. 이러한 라이브러리는 staleTime 같은 옵션을 사용하여 데이터의 신선도를 제어하는 경우가 많습니다.

외부 캐시가 신선도를 결정하도록 하려면 routerOptions.defaultPreloadStaleTime 또는 routeOptions.preloadStaleTime0으로 설정합니다. 그러면 완료된 프리로드 데이터가 Router에서 즉시 stale 상태가 되지만 보존은 여전히 preloadGcTime을 따릅니다. 겹치는 프리로드 또는 탐색 소비자는 진행 중인 로더 작업을 계속 공유할 수 있고 shouldReload는 계속 로더 호출을 억제할 수 있습니다.

예시는 다음과 같습니다.

React

import { createRouter } from '@tanstack/react-router'

const router = createRouter({
// ...
defaultPreloadStaleTime: 0,
})

Solid

import { createRouter } from '@tanstack/solid-router'

const router = createRouter({
// ...
defaultPreloadStaleTime: 0,
})

그러면 예를 들어 React Query의 staleTime 같은 옵션을 사용하여 프리로드의 신선도를 제어할 수 있습니다.

수동으로 프리로딩

라우트를 수동으로 프리로드해야 한다면 라우터의 preloadRoute 메서드를 사용합니다. 이 메서드는 표준 TanStack NavigateOptions 객체를 받고 추측성 매치 레인을 반환합니다. 로드 중 발생한 일반 오류나 not-found는 반환된 레인에 나타나며, 재사용 가능한 레인을 만들지 않는 취소 또는 제어 흐름은 undefined를 반환할 수 있습니다.

React

import { isNotFound } from '@tanstack/react-router'

function Component() {
const router = useRouter()

useEffect(() => {
async function preload() {
const matches = await router.preloadRoute({
to: postRoute,
params: { id: 1 },
})

const routeFailure = matches?.find(
(match) =>
match.status === 'error' ||
match.status === 'notFound' ||
isNotFound(match.error),
)

if (routeFailure) {
// Inspect routeFailure.error
}
}

preload()
}, [router])

return <div />
}

Solid

import { isNotFound } from '@tanstack/solid-router'

function Component() {
const router = useRouter()

createEffect(() => {
async function preload() {
const matches = await router.preloadRoute({
to: postRoute,
params: { id: 1 },
})

const routeFailure = matches?.find(
(match) =>
match.status === 'error' ||
match.status === 'notFound' ||
isNotFound(match.error),
)

if (routeFailure) {
// Inspect routeFailure.error
}
}

preload()
})

return <div />
}

라우트의 JS 청크만 프리로드해야 한다면 라우터의 loadRouteChunk 메서드를 사용할 수 있습니다. 라우트 객체를 받고 라우트 청크가 로드되면 확인되는 promise를 반환합니다.

React

function Component() {
const router = useRouter()

useEffect(() => {
async function preloadRouteChunks() {
try {
const postsRoute = router.routesByPath['/posts']
await Promise.all([
router.loadRouteChunk(router.routesByPath['/']),
router.loadRouteChunk(postsRoute),
router.loadRouteChunk(postsRoute.parentRoute),
])
} catch (err) {
// Failed to preload route chunk
}
}

preloadRouteChunks()
}, [router])

return <div />
}

Solid

function Component() {
const router = useRouter()

createEffect(() => {
async function preloadRouteChunks() {
try {
const postsRoute = router.routesByPath['/posts']
await Promise.all([
router.loadRouteChunk(router.routesByPath['/']),
router.loadRouteChunk(postsRoute),
router.loadRouteChunk(postsRoute.parentRoute),
])
} catch (err) {
// Failed to preload route chunk
}
}

preloadRouteChunks()
})

return <div />
}