Framework
Version

Preloading

Preloading in TanStack Router is a way to load a route before the user actually navigates to it. This is useful for routes that are likely to be visited by the user next. For example, if you have a list of posts and the user is likely to click on one of them, you can preload the post route so that it's ready to go when the user clicks on it.

Supported Preloading Strategies

  • Intent
    • Preloading by "intent" works by using hover and touch start events on <Link> components to preload the dependencies for the destination route.
    • This strategy is useful for preloading routes that the user is likely to visit next.
  • Viewport Visibility
    • Preloading by "viewport" works by using the Intersection Observer API to preload the dependencies for the destination route when the <Link> component is in the viewport.
    • This strategy is useful for preloading routes that are below the fold or off-screen.
  • Render
    • Preloading by "render" works by preloading the dependencies for the destination route as soon as the <Link> component is rendered in the DOM.
    • This strategy is useful for preloading routes that are always needed.

How long does preloaded data stay in memory?

Preloaded route matches are temporarily cached in memory with a few important caveats:

  • Unused preloaded data is removed after 30 seconds by default. This can be configured by setting the defaultPreloadMaxAge option on your router.
  • Obviously, when a a route is loaded, its preloaded version is promoted to the router's normal pending matches state.

If you need more control over preloading, caching and/or garbage collection of preloaded data, you should use an external caching library like TanStack Query.

The simplest way to preload routes for your application is to set the defaultPreload option to intent for your entire router:

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

const router = createRouter({
  // ...
  defaultPreload: 'intent',
})
import { createRouter } from '@tanstack/react-router'

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

This will turn on intent preloading by default for all <Link> components in your application. You can also set the preload prop on individual <Link> components to override the default behavior.

Preload Delay

By default, preloading will start after 50ms of the user hovering or touching a <Link> component. You can change this delay by setting the defaultPreloadDelay option on your router:

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

const router = createRouter({
  // ...
  defaultPreloadDelay: 100,
})
import { createRouter } from '@tanstack/react-router'

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

You can also set the preloadDelay prop on individual <Link> components to override the default behavior on a per-link basis.

Built-in Preloading & preloadStaleTime

If you're using the built-in loaders, you can control how long preloaded data is considered fresh until another preload is triggered by setting either routerOptions.defaultPreloadStaleTime or routeOptions.preloadStaleTime to a number of milliseconds. By default, preloaded data is considered fresh for 30 seconds..

To change this, you can set the defaultPreloadStaleTime option on your router:

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

const router = createRouter({
  // ...
  defaultPreloadStaleTime: 10_000,
})
import { createRouter } from '@tanstack/react-router'

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

Or, you can use the routeOptions.preloadStaleTime option on individual routes:

tsx
// src/routes/posts.$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => fetchPost(params.postId),
  // Preload the route again if the preload cache is older than 10 seconds
  preloadStaleTime: 10_000,
})
// src/routes/posts.$postId.tsx
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => fetchPost(params.postId),
  // Preload the route again if the preload cache is older than 10 seconds
  preloadStaleTime: 10_000,
})

Preloading with External Libraries

When integrating external caching libraries like React Query, which have their own mechanisms for determining stale data, you may want to override the default preloading and stale-while-revalidate logic of TanStack Router. These libraries often use options like staleTime to control the freshness of data.

To customize the preloading behavior in TanStack Router and fully leverage your external library's caching strategy, you can bypass the built-in caching by setting routerOptions.defaultPreloadStaleTime or routeOptions.preloadStaleTime to 0. This ensures that all preloads are marked as stale internally, and loaders are always invoked, allowing your external library, such as React Query, to manage data loading and caching.

For example:

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

const router = createRouter({
  // ...
  defaultPreloadStaleTime: 0,
})
import { createRouter } from '@tanstack/react-router'

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

This would then allow you, for instance, to use an option like React Query's staleTime to control the freshness of your preloads.

Preloading Manually

If you need to manually preload a route, you can use the router's preloadRoute method. It accepts a standard TanStack NavigateOptions object and returns a promise that resolves when the route is preloaded.

tsx
function Component() {
  const router = useRouter()

  useEffect(() => {
    try {
      const matches = await router.preloadRoute({
        to: postRoute,
        params: { id: 1 },
      })
    } catch (err) {
      // Failed to preload route
    }
  }, [])

  return <div />
}
function Component() {
  const router = useRouter()

  useEffect(() => {
    try {
      const matches = await router.preloadRoute({
        to: postRoute,
        params: { id: 1 },
      })
    } catch (err) {
      // Failed to preload route
    }
  }, [])

  return <div />
}

If you need to preload only the JS chunk of a route, you can use the router's loadRouteChunk method. It accepts a route object and returns a promise that resolves when the route chunk is loaded.

tsx
function Component() {
  const router = useRouter()

  useEffect(() => {
    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
    }
  }, [])

  return <div />
}
function Component() {
  const router = useRouter()

  useEffect(() => {
    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
    }
  }, [])

  return <div />
}
Subscribe to Bytes

Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.

Bytes

No spam. Unsubscribe at any time.