Code Splitting

Purpose and Scope

Code splitting in TanStack Router is the route-level performance strategy for keeping the initial JavaScript payload small while still preserving Router features such as matching, loading, validation, and typed navigation. The Router docs frame the goal in practical terms: load less code on the first page view, load additional code only when it is needed, and produce smaller chunks that browsers can cache more effectively. In a Router app, this matters most as the route tree grows. Without splitting, every route component and error UI can become part of the startup cost even when the user only visits one route.

Sources: docs/router/guide/code-splitting.md, docs/router/guide/automatic-code-splitting.md

TanStack Router approaches this problem by separating a route definition into critical and non-critical configuration. Critical route configuration is needed early so the router can parse paths, validate search params, run before-load logic, start loaders, build route context, expose links, and apply static route data. Non-critical configuration is the UI that does not need to participate in matching or data kickoff: the route component, pending component, error component, and not-found component. This separation is the conceptual basis for both automatic splitting and the explicit lazy-route APIs.

Sources: docs/router/guide/code-splitting.md

Relevant Source Files

  • docs/router/guide/code-splitting.md - Main guide for route-level code splitting, critical versus lazy route configuration, file-based splitting approaches, directory encapsulation, and loader tradeoffs.
  • docs/router/guide/automatic-code-splitting.md - Detailed guide for enabling autoCodeSplitting, explaining reference files, virtual files, split groupings, and build-time transformations.
  • docs/router/api/router/createLazyFileRouteFunction.md - API reference for defining a lazily loaded partial file route that may configure non-critical route properties.
  • docs/router/api/router/lazyRouteComponentFunction.md - API reference for creating one-off lazy route components, especially for code-based routing or component-level imports.

Critical and Lazy Route Configuration

The important design rule is that route matching and data startup should not be delayed by fetching a component chunk. The guide explicitly keeps loaders in the critical category by default. A loader is already asynchronous, so splitting it can add a second delay: first downloading the loader chunk, then waiting for the loader itself. The docs also call loaders highly preloadable assets, especially when an app uses preload intent such as hovering over a link. Keeping them immediately available helps the router begin work before the user completes navigation.

Sources: docs/router/guide/code-splitting.md

For file-based routing, the docs also recommend organizing a route and its supporting files in a directory when that makes splitting easier to reason about. A route file such as a posts route can be moved into a directory with the same route name and renamed to a route file inside that directory. This does not require additional configuration because the file-based routing system supports both flat and nested file structures. The practical result is that a route can keep its component, lazy companion files, and supporting modules nearby without changing the route tree semantics.

Sources: docs/router/guide/code-splitting.md

Automatic Code Splitting

Automatic code splitting is the easiest path when an app uses file-based routing with a supported bundler plugin. The Router docs are explicit that this capability belongs to the bundler integration, not to the CLI alone. Enabling it is a configuration choice on the TanStack Router bundler plugin. In a Vite setup, the TanStack Router plugin should receive autoCodeSplitting: true; the guide also notes that framework plugins such as the React plugin should be placed after the Router bundler plugin so the Router transform can run at the right stage.

Sources: docs/router/guide/code-splitting.md, docs/router/guide/automatic-code-splitting.md

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
 
export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
    }),
    react(),
  ],
})

Once enabled, the bundler plugin transforms route files during development and build. The automatic splitting guide describes two outputs for each processed route file. The reference file is the rewritten version of the original route file, where properties such as component and pending UI point at lazy-loading wrappers. The virtual file is generated when the bundler resolves a special split request for a specific property. This lets the original source remain readable while the bundle output contains small, demand-loaded chunks for the pieces selected by the transform.

Sources: docs/router/guide/automatic-code-splitting.md

The same guide introduces split groupings, which are arrays of route property names that determine what gets bundled together. Available split properties include the route component, error component, pending component, not-found component, and loader. The default model follows the broader Router guidance by focusing on UI-heavy non-critical properties, but the grouping concept gives teams a way to tune chunk boundaries for their own usage patterns. For example, a large route component and its error UI may be split separately or grouped depending on cache behavior and navigation frequency.

Sources: docs/router/guide/automatic-code-splitting.md

Lazy File Routes and Generated APIs

For file-based routing without relying entirely on the automatic transform, createLazyFileRoute creates a partial route instance that is loaded when the route is matched. Its scope is intentionally narrow: the API can configure only non-critical properties such as component, pending component, error component, and not-found component. The path argument is required by the type contract, but the docs state that it is automatically inserted and updated by the route generation commands. For generation to work correctly, the file route instance must be exported with the Route identifier.

Sources: docs/router/api/router/createLazyFileRouteFunction.md

import { createLazyFileRoute } from '@tanstack/react-router'
 
export const Route = createLazyFileRoute('/')({
  component: IndexComponent,
})
 
function IndexComponent() {
  const data = Route.useLoaderData()
  return <div>{data}</div>
}

The return type of createLazyFileRoute mirrors the conceptual split in the guide. It accepts a partial selection of route options for the lazy portion only, specifically the UI-related properties that do not need to run before matching and data startup. That makes the API safe to use with generated file routes: the main file can continue to provide the critical configuration, while the lazy file contributes renderable pieces after the router has identified the match. This is also why the API page describes it as a partial file-based route instance rather than a full route definition.

Sources: docs/router/api/router/createLazyFileRouteFunction.md

One-Off Lazy Components

For code-based routing, or for a single component that should be split without creating a lazy file route, lazyRouteComponent is the direct helper. The API accepts an importer function returning a promise for a module and an optional export name. If no export name is provided, the helper loads the default export. It returns a React lazy component with an additional preload method, which means route code can participate in Router preloading patterns rather than behaving like an isolated dynamic import hidden inside a component tree.

Sources: docs/router/api/router/lazyRouteComponentFunction.md

import { lazyRouteComponent } from '@tanstack/react-router'
 
const route = createRoute({
  path: '/posts/$postId',
  component: lazyRouteComponent(() => import('./Post')),
})
 
const namedExportRoute = createRoute({
  path: '/posts/$postId',
  component: lazyRouteComponent(
    () => import('./Post'),
    'PostByIdPageComponent',
  ),
})

The lazy component helper is deliberately positioned as secondary for file-based apps. The API reference recommends createLazyFileRoute when the app is already using file-based routing, because the generated route tree and route file conventions can preserve type safety and keep critical configuration in the right place. lazyRouteComponent is still useful for code-based routes, incremental migration, or highly targeted chunking, but it does not replace the file-route lifecycle. Treat it as a focused component import tool rather than the main file-based route splitting mechanism.

Sources: docs/router/api/router/lazyRouteComponentFunction.md

Implementation Flow for an App

A practical route-splitting workflow starts by deciding whether the app is file-based or code-based. In a file-based app with a supported bundler, enable automatic splitting first because it requires the least route-level ceremony and can transform existing route definitions. Then review large routes and decide whether directory encapsulation would make the source easier to maintain. If a route needs explicit lazy UI files, use createLazyFileRoute and export the partial route as Route. If the app is code-based, wrap individual expensive route components with lazyRouteComponent and keep loaders available outside that lazy boundary.

Sources: docs/router/guide/code-splitting.md, docs/router/api/router/createLazyFileRouteFunction.md, docs/router/api/router/lazyRouteComponentFunction.md

The main edge case is loader splitting. The automatic guide lists loader among properties that can participate in split groupings, while the main code-splitting guide explains why the default recommendation is not to split loaders. Teams should choose loader splitting only when they understand the extra asynchronous boundary and have measured that the loader code itself is a meaningful bundle contributor. For most routes, splitting render components produces the largest startup improvement while preserving early data loading, preload behavior, and route matching responsiveness.

Sources: docs/router/guide/code-splitting.md, docs/router/guide/automatic-code-splitting.md

Compact API Reference

API or optionWhere it is usedContract
autoCodeSplittingTanStack Router bundler plugin configurationEnables automatic route-file transformation for supported bundlers in file-based routing apps.
createLazyFileRoute(path)File-based lazy route filesReturns a function accepting non-critical route options such as component, pendingComponent, errorComponent, and notFoundComponent.
path for createLazyFileRouteGenerated file route APIRequired string representing the full file route path; maintained by tsr generate and tsr watch.
Route export identifierFile-based generated routesRequired export name so generation and watch commands can update route instances correctly.
lazyRouteComponent(importer, exportName?)Code-based routes or one-off lazy componentsReturns a React lazy component with preload(), loading the default export or a named export from the imported module.

Next Steps

Use this page together with the file-based routing and route generation material when designing the structure of a large app. If the goal is bundle-size reduction with minimal source changes, start with the automatic plugin option and inspect the generated chunks in the bundler output. If the goal is explicit ownership over lazy UI boundaries, use lazy file routes for file-based routing and lazyRouteComponent for code-based definitions. For related concepts, continue to pages on file-based routing, router plugin and route generation, data loading, preloading and caching, and SSR behavior.