The automatic code splitting feature in TanStack Router allows you to optimize your application's bundle size by lazily loading route components and their associated data. This is particularly useful for large applications where you want to minimize the initial load time by only loading the necessary code for the current route.
To turn this feature on, simply set the autoCodeSplitting option to true in your bundler plugin configuration. This enables the router to automatically handle code splitting for your routes without requiring any additional setup.
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
autoCodeSplitting: true, // Enable automatic code splitting
}),
],
})
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
autoCodeSplitting: true, // Enable automatic code splitting
}),
],
})
But that's just the beginning! TanStack Router's automatic code splitting is not only easy to enable, but it also provides powerful customization options to tailor how your routes are split into chunks. This allows you to optimize your application's performance based on your specific needs and usage patterns.
TanStack Router's automatic code splitting works by transforming your route files both during 'development' and at 'build' time. It rewrites the route definitions to use lazy-loading wrappers for components and loaders, which allows the bundler to group these properties into separate chunks.
Tip
A chunk is a file that contains a portion of your application's code, which can be loaded on demand. This helps reduce the initial load time of your application by only loading the code that is needed for the current route.
So when your application loads, it doesn't include all the code for every route. Instead, it only includes the code for the routes that are initially needed. As users navigate through your application, additional chunks are loaded on demand.
This happens seamlessly, without requiring you to manually split your code or manage lazy loading. The TanStack Router bundler plugin takes care of everything, ensuring that your routes are optimized for performance right out of the box.
When you enable automatic code splitting, the bundler plugin does this by using static code analysis look at your the code in your route files to transform them into optimized outputs.
This transformation process produces two key outputs when each of your route files are processed:
This process ensures that your original code remains clean and readable, while the actual bundled output is optimized for initial bundle size.
The decision of what to split into separate chunks is crucial for optimizing your application's performance. TanStack Router uses a concept called "Split Groupings" to determine how different parts of your route should be bundled together.
Split groupings are arrays of properties that tell TanStack Router how to bundle different parts of your route together. Each grouping is an list of property names that you want to bundle together into a single lazy-loaded chunk.
The available properties to split are:
By default, TanStack Router uses the following split groupings:
[
['component'],
['errorComponent'],
['notFoundComponent']
]
[
['component'],
['errorComponent'],
['notFoundComponent']
]
This means that it creates three separate lazy-loaded chunks for each route. Resulting in:
For most applications, the default behavior of using autoCodeSplitting: true is sufficient. However, TanStack Router provides several options to customize how your routes are split into chunks, allowing you to optimize for specific use cases or performance needs.
You can change how TanStack Router splits your routes by changing the defaultBehavior option in your bundler plugin configuration. This allows you to define how different properties of your routes should be bundled together.
For example, to bundle all UI-related components into a single chunk, you could configure it like this:
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
autoCodeSplitting: true,
codeSplittingOptions: {
defaultBehavior: [
[
'component',
'pendingComponent',
'errorComponent',
'notFoundComponent',
], // Bundle all UI components together
],
},
}),
],
})
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
autoCodeSplitting: true,
codeSplittingOptions: {
defaultBehavior: [
[
'component',
'pendingComponent',
'errorComponent',
'notFoundComponent',
], // Bundle all UI components together
],
},
}),
],
})
For complex rulesets, you can use the splitBehavior function in your vite config to programmatically define how routes should be split into chunks based on their routeId. This function allows you to implement custom logic for grouping properties together, giving you fine-grained control over the code splitting behavior.
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
autoCodeSplitting: true,
codeSplittingOptions: {
splitBehavior: ({ routeId }) => {
// For all routes under /posts, bundle the loader and component together
if (routeId.startsWith('/posts')) {
return [['loader', 'component']]
}
// All other routes will use the `defaultBehavior`
},
},
}),
],
})
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
autoCodeSplitting: true,
codeSplittingOptions: {
splitBehavior: ({ routeId }) => {
// For all routes under /posts, bundle the loader and component together
if (routeId.startsWith('/posts')) {
return [['loader', 'component']]
}
// All other routes will use the `defaultBehavior`
},
},
}),
],
})
For ultimate control, you can override the global configuration directly inside a route file by adding a codeSplitGroupings property. This is useful for routes that have unique optimization needs.
// src/routes/posts.route.tsx
import { createFileRoute } from '@tanstack/react-router'
import { loadPostsData } from './-heavy-posts-utils'
export const Route = createFileRoute('/posts')({
// For this specific route, bundle the loader and component together.
codeSplitGroupings: [['loader', 'component']],
loader: () => loadPostsData(),
component: PostsComponent,
})
function PostsComponent() {
// ...
}
// src/routes/posts.route.tsx
import { createFileRoute } from '@tanstack/react-router'
import { loadPostsData } from './-heavy-posts-utils'
export const Route = createFileRoute('/posts')({
// For this specific route, bundle the loader and component together.
codeSplitGroupings: [['loader', 'component']],
loader: () => loadPostsData(),
component: PostsComponent,
})
function PostsComponent() {
// ...
}
This will create a single chunk that includes both the loader and the component for this specific route, overriding both the default behavior and any programmatic split behavior defined in your bundler config.
This guide has so far describe three different ways to configure how TanStack Router splits your routes into chunks.
To make sure that the different configurations do not conflict with each other, TanStack Router uses the following order of precedence:
The loader function is responsible for fetching data needed by the route. By default, it is bundled with into your "reference file" and loaded in the initial bundle. However, you can also split the loader into its own chunk if you want to optimize further.
Caution
Moving the loader into its own chunk is a performance trade-off. It introduces an additional trip to the server before the data can be fetched, which can lead to slower initial page loads. This is because the loader must be fetched and executed before the route can render its component. Therefore, we recommend keeping the loader in the initial bundle unless you have a specific reason to split it.
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
autoCodeSplitting: true,
codeSplittingOptions: {
defaultBehavior: [
['loader'], // The loader will be in its own chunk
['component'],
// ... other component groupings
],
},
}),
],
})
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
tanstackRouter({
autoCodeSplitting: true,
codeSplittingOptions: {
defaultBehavior: [
['loader'], // The loader will be in its own chunk
['component'],
// ... other component groupings
],
},
}),
],
})
We highly discourage splitting the loader unless you have a specific use case that requires it. In most cases, keeping the loader bundled with the component is the best choice for performance.
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.