Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tanstack/router/llms.txt

Use this file to discover all available pages before exploring further.

Code splitting breaks your application into smaller chunks that are loaded on demand, reducing initial bundle size and improving load times.

Lazy routes

Split route components using createLazyRoute:
src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
import { fetchPost } from '@/api'

// Keep loader in main bundle (runs on server)
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => {
    return await fetchPost(params.postId)
  },
}).lazy(() => import('./posts.$postId.lazy'))
src/routes/posts/$postId.lazy.tsx
import { createLazyFileRoute } from '@tanstack/react-router'

// Component code is lazy-loaded
export const Route = createLazyFileRoute('/posts/$postId')({
  component: PostComponent,
})

function PostComponent() {
  const post = Route.useLoaderData()
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

Code-based lazy routes

For code-based routing:
import { createRoute } from '@tanstack/react-router'

const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
  loader: async () => await fetchPosts(),
}).lazy(() => import('./routes/posts.lazy').then((m) => m.Route))
src/routes/posts.lazy.tsx
import { createLazyRoute } from '@tanstack/react-router'

export const Route = createLazyRoute('/posts')({
  component: PostsList,
  errorComponent: PostsError,
  pendingComponent: PostsLoading,
})

function PostsList() {
  const posts = Route.useLoaderData()
  return <div>{/* posts list */}</div>
}

Lazy route components

Lazy-load just the component:
import { lazyRouteComponent } from '@tanstack/react-router'

export const Route = createFileRoute('/dashboard')({
  component: lazyRouteComponent(() => import('./Dashboard')),
})
src/routes/Dashboard.tsx
export default function Dashboard() {
  return <div>Dashboard</div>
}

What to split

Critical loader logic

Keep loaders in the main bundle for SSR:
// ✅ Good - Loader in main bundle
export const Route = createFileRoute('/posts')({
  loader: async () => await fetchPosts(),
}).lazy(() => import('./posts.lazy'))
// ❌ Bad - Loader in lazy bundle (breaks SSR)
export const Route = createLazyFileRoute('/posts')({
  loader: async () => await fetchPosts(),
  component: Posts,
})

Heavy components

Split large component libraries:
import { lazyRouteComponent } from '@tanstack/react-router'

export const Route = createFileRoute('/editor')({
  // Monaco Editor is large - lazy load it
  component: lazyRouteComponent(() => import('./CodeEditor')),
})

Route groups

Split entire route sections:
// Admin routes are lazy-loaded as a group
const adminRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/admin',
}).lazy(() => import('./admin/routes'))

Preloading strategies

Control when chunks are loaded:
import { Link } from '@tanstack/react-router'

function Navigation() {
  return (
    <nav>
      {/* Preload on hover */}
      <Link to="/dashboard" preload="intent">
        Dashboard
      </Link>
      
      {/* Preload when visible */}
      <Link to="/reports" preload="viewport">
        Reports
      </Link>
      
      {/* Preload immediately */}
      <Link to="/settings" preload="render">
        Settings
      </Link>
      
      {/* Don't preload */}
      <Link to="/archive" preload={false}>
        Archive
      </Link>
    </nav>
  )
}

Manual preloading

Preload routes programmatically:
import { useRouter } from '@tanstack/react-router'

function App() {
  const router = useRouter()
  
  React.useEffect(() => {
    // Preload after initial render
    setTimeout(() => {
      router.preloadRoute({
        to: '/dashboard',
      })
    }, 1000)
  }, [])
  
  return <RouterProvider router={router} />
}

Suspense boundaries

Control loading states during code splitting:
import { Suspense } from 'react'
import { Outlet } from '@tanstack/react-router'

function Layout() {
  return (
    <div>
      <Header />
      <Suspense fallback={<PageLoader />}>
        <Outlet />
      </Suspense>
    </div>
  )
}

Pending components

Show loading state while lazy chunks load:
export const Route = createFileRoute('/heavy-page')({
  component: lazyRouteComponent(() => import('./HeavyPage')),
  pendingComponent: () => (
    <div className="loading">
      <Spinner />
      <p>Loading page...</p>
    </div>
  ),
})

Bundle analysis

Analyze bundle sizes to find optimization opportunities:
vite.config.ts
import { defineConfig } from 'vite'
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    visualizer({
      open: true,
      gzipSize: true,
      brotliSize: true,
    }),
  ],
})

Route-level splitting

Ensure each route is in its own chunk:
vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // Split routes into separate chunks
          if (id.includes('src/routes/')) {
            const route = id.split('src/routes/')[1].split('/')[0]
            return `route-${route}`
          }
        },
      },
    },
  },
})

Dynamic imports

Lazy-load utilities and helpers:
function DataExport() {
  const handleExport = async () => {
    // Only load export library when needed
    const { exportToCSV } = await import('@/utils/export')
    exportToCSV(data)
  }
  
  return <button onClick={handleExport}>Export</button>
}

Best practices

Loaders should not be lazy-loaded - they need to run on the server.
Each route should be its own chunk for optimal caching.
Use preload=“intent” or preload=“viewport” for frequently accessed routes.
Wrap lazy routes in Suspense to show loading states.
Regularly check bundle sizes and split large chunks.

Performance tips

Target bundle size: Aim for chunks under 200KB. Larger chunks should be split further.
Prefetch on idle: Use requestIdleCallback to preload routes during browser idle time.
Over-splitting can hurt performance. Balance chunk count with chunk size.

Next steps

File-based routing

Learn automatic route splitting

Vite plugin

Configure code splitting