@tanstack/router-core 1.167.2 → 1.167.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ ---
2
+ name: router-core/code-splitting
3
+ description: >-
4
+ Automatic code splitting (autoCodeSplitting), .lazy.tsx convention,
5
+ createLazyFileRoute, createLazyRoute, lazyRouteComponent, getRouteApi
6
+ for typed hooks in split files, codeSplitGroupings per-route override,
7
+ splitBehavior programmatic config, critical vs non-critical properties.
8
+ type: sub-skill
9
+ library: tanstack-router
10
+ library_version: '1.166.2'
11
+ requires:
12
+ - router-core
13
+ sources:
14
+ - TanStack/router:docs/router/guide/code-splitting.md
15
+ - TanStack/router:docs/router/guide/automatic-code-splitting.md
16
+ ---
17
+
18
+ # Code Splitting
19
+
20
+ TanStack Router separates route code into **critical** (required to match and start loading) and **non-critical** (can be lazy-loaded). The bundler plugin can split automatically, or you can split manually with `.lazy.tsx` files.
21
+
22
+ > **CRITICAL**: Never `export` component functions from route files — exported functions are included in the main bundle and bypass code splitting entirely.
23
+
24
+ > **CRITICAL**: Use `getRouteApi('/path')` in code-split files, NOT `import { Route } from './route'`. Importing Route defeats code splitting.
25
+
26
+ ## What Stays in the Main Bundle (Critical)
27
+
28
+ - Path parsing/serialization
29
+ - `validateSearch`
30
+ - `loader`, `beforeLoad`
31
+ - Route context, static data
32
+ - Links, scripts, styles
33
+
34
+ ## What Gets Split (Non-Critical)
35
+
36
+ - `component`
37
+ - `errorComponent`
38
+ - `pendingComponent`
39
+ - `notFoundComponent`
40
+
41
+ > The `loader` is NOT split by default. It is already async, so splitting it adds a double async cost: fetch the chunk, then execute the loader. Only split the loader if you have a specific reason.
42
+
43
+ ## Setup: Automatic Code Splitting
44
+
45
+ Enable `autoCodeSplitting: true` in the bundler plugin. This is the recommended approach.
46
+
47
+ ```ts
48
+ // vite.config.ts
49
+ import { defineConfig } from 'vite'
50
+ import react from '@vitejs/plugin-react'
51
+ import { tanstackRouter } from '@tanstack/router-plugin/vite'
52
+
53
+ export default defineConfig({
54
+ plugins: [
55
+ // TanStack Router plugin MUST come before the framework plugin
56
+ tanstackRouter({
57
+ autoCodeSplitting: true,
58
+ }),
59
+ react(),
60
+ ],
61
+ })
62
+ ```
63
+
64
+ With this enabled, route files are automatically transformed. Components are split into separate chunks; loaders stay in the main bundle. No `.lazy.tsx` files needed.
65
+
66
+ ```tsx
67
+ // src/routes/posts.tsx — everything in one file, splitting is automatic
68
+ import { createFileRoute } from '@tanstack/react-router'
69
+ import { fetchPosts } from '../api'
70
+
71
+ export const Route = createFileRoute('/posts')({
72
+ loader: fetchPosts,
73
+ component: PostsComponent,
74
+ })
75
+
76
+ // NOT exported — this is critical for automatic code splitting to work
77
+ function PostsComponent() {
78
+ const posts = Route.useLoaderData()
79
+ return (
80
+ <ul>
81
+ {posts.map((post) => (
82
+ <li key={post.id}>{post.title}</li>
83
+ ))}
84
+ </ul>
85
+ )
86
+ }
87
+ ```
88
+
89
+ ## Manual Splitting with `.lazy.tsx`
90
+
91
+ If you cannot use automatic code splitting (e.g. CLI-only, no bundler plugin), split manually into two files:
92
+
93
+ ```tsx
94
+ // src/routes/posts.tsx — critical route config only
95
+ import { createFileRoute } from '@tanstack/react-router'
96
+ import { fetchPosts } from '../api'
97
+
98
+ export const Route = createFileRoute('/posts')({
99
+ loader: fetchPosts,
100
+ })
101
+ ```
102
+
103
+ ```tsx
104
+ // src/routes/posts.lazy.tsx — non-critical (lazy-loaded)
105
+ import { createLazyFileRoute } from '@tanstack/react-router'
106
+
107
+ export const Route = createLazyFileRoute('/posts')({
108
+ component: PostsComponent,
109
+ })
110
+
111
+ function PostsComponent() {
112
+ // Use getRouteApi to access typed hooks without importing Route
113
+ return <div>Posts</div>
114
+ }
115
+ ```
116
+
117
+ `createLazyFileRoute` supports only: `component`, `errorComponent`, `pendingComponent`, `notFoundComponent`.
118
+
119
+ ## Virtual Routes
120
+
121
+ If splitting leaves the critical route file empty, delete it entirely. A virtual route is auto-generated in `routeTree.gen.ts`:
122
+
123
+ ```tsx
124
+ // src/routes/about.lazy.tsx — no about.tsx needed
125
+ import { createLazyFileRoute } from '@tanstack/react-router'
126
+
127
+ export const Route = createLazyFileRoute('/about')({
128
+ component: () => <h1>About Us</h1>,
129
+ })
130
+ ```
131
+
132
+ ## Code-Based Splitting
133
+
134
+ For code-based (non-file-based) routing, use `createLazyRoute` and the `.lazy()` method:
135
+
136
+ ```tsx
137
+ // src/posts.lazy.tsx
138
+ import { createLazyRoute } from '@tanstack/react-router'
139
+
140
+ export const Route = createLazyRoute('/posts')({
141
+ component: PostsComponent,
142
+ })
143
+
144
+ function PostsComponent() {
145
+ return <div>Posts</div>
146
+ }
147
+ ```
148
+
149
+ ```tsx
150
+ // src/app.tsx
151
+ import { createRoute } from '@tanstack/react-router'
152
+
153
+ const postsRoute = createRoute({
154
+ getParentRoute: () => rootRoute,
155
+ path: '/posts',
156
+ }).lazy(() => import('./posts.lazy').then((d) => d.Route))
157
+ ```
158
+
159
+ ## Accessing Typed Hooks in Split Files: `getRouteApi`
160
+
161
+ When your component lives in a separate file, use `getRouteApi` to get typed access to route hooks without importing the Route object:
162
+
163
+ ```tsx
164
+ // src/routes/posts.lazy.tsx
165
+ import { createLazyFileRoute, getRouteApi } from '@tanstack/react-router'
166
+
167
+ const routeApi = getRouteApi('/posts')
168
+
169
+ export const Route = createLazyFileRoute('/posts')({
170
+ component: PostsComponent,
171
+ })
172
+
173
+ function PostsComponent() {
174
+ const posts = routeApi.useLoaderData()
175
+ const { page } = routeApi.useSearch()
176
+ const params = routeApi.useParams()
177
+ const context = routeApi.useRouteContext()
178
+ return <div>Posts page {page}</div>
179
+ }
180
+ ```
181
+
182
+ `getRouteApi` provides: `useLoaderData`, `useLoaderDeps`, `useMatch`, `useParams`, `useRouteContext`, `useSearch`.
183
+
184
+ ## Per-Route Split Overrides: `codeSplitGroupings`
185
+
186
+ Override split behavior for a specific route by adding `codeSplitGroupings` directly in the route file:
187
+
188
+ ```tsx
189
+ // src/routes/posts.tsx
190
+ import { createFileRoute } from '@tanstack/react-router'
191
+ import { loadPostsData } from './-heavy-posts-utils'
192
+
193
+ export const Route = createFileRoute('/posts')({
194
+ // Bundle loader and component together for this route
195
+ codeSplitGroupings: [['loader', 'component']],
196
+ loader: () => loadPostsData(),
197
+ component: PostsComponent,
198
+ })
199
+
200
+ function PostsComponent() {
201
+ const data = Route.useLoaderData()
202
+ return <div>{data.title}</div>
203
+ }
204
+ ```
205
+
206
+ ## Global Split Configuration
207
+
208
+ ### `defaultBehavior` — Change Default Groupings
209
+
210
+ ```ts
211
+ // vite.config.ts
212
+ import { defineConfig } from 'vite'
213
+ import { tanstackRouter } from '@tanstack/router-plugin/vite'
214
+
215
+ export default defineConfig({
216
+ plugins: [
217
+ tanstackRouter({
218
+ autoCodeSplitting: true,
219
+ codeSplittingOptions: {
220
+ defaultBehavior: [
221
+ // Bundle all UI components into one chunk
222
+ [
223
+ 'component',
224
+ 'pendingComponent',
225
+ 'errorComponent',
226
+ 'notFoundComponent',
227
+ ],
228
+ ],
229
+ },
230
+ }),
231
+ ],
232
+ })
233
+ ```
234
+
235
+ ### `splitBehavior` — Programmatic Per-Route Logic
236
+
237
+ ```ts
238
+ // vite.config.ts
239
+ import { defineConfig } from 'vite'
240
+ import { tanstackRouter } from '@tanstack/router-plugin/vite'
241
+
242
+ export default defineConfig({
243
+ plugins: [
244
+ tanstackRouter({
245
+ autoCodeSplitting: true,
246
+ codeSplittingOptions: {
247
+ splitBehavior: ({ routeId }) => {
248
+ if (routeId.startsWith('/posts')) {
249
+ return [['loader', 'component']]
250
+ }
251
+ // All other routes use defaultBehavior
252
+ },
253
+ },
254
+ }),
255
+ ],
256
+ })
257
+ ```
258
+
259
+ ### Precedence Order
260
+
261
+ 1. Per-route `codeSplitGroupings` (highest)
262
+ 2. `splitBehavior` function
263
+ 3. `defaultBehavior` option (lowest)
264
+
265
+ ## Common Mistakes
266
+
267
+ ### 1. HIGH: Exporting component functions prevents code splitting
268
+
269
+ ```tsx
270
+ // WRONG — export puts PostsComponent in the main bundle
271
+ export function PostsComponent() {
272
+ return <div>Posts</div>
273
+ }
274
+
275
+ // CORRECT — no export, function stays in the split chunk
276
+ function PostsComponent() {
277
+ return <div>Posts</div>
278
+ }
279
+ ```
280
+
281
+ ### 2. MEDIUM: Trying to code-split the root route
282
+
283
+ `__root.tsx` does not support code splitting. It is always rendered regardless of the current route. Do not create `__root.lazy.tsx`.
284
+
285
+ ### 3. MEDIUM: Splitting the loader adds double async cost
286
+
287
+ ```tsx
288
+ // AVOID unless you have a specific reason
289
+ codeSplittingOptions: {
290
+ defaultBehavior: [
291
+ ['loader'], // Fetch chunk THEN execute loader = two network waterfalls
292
+ ['component'],
293
+ ],
294
+ }
295
+
296
+ // PREFERRED — loader stays in main bundle (default behavior)
297
+ codeSplittingOptions: {
298
+ defaultBehavior: [
299
+ ['component'],
300
+ ['errorComponent'],
301
+ ['notFoundComponent'],
302
+ ],
303
+ }
304
+ ```
305
+
306
+ ### 4. HIGH: Importing Route in code-split files for typed hooks
307
+
308
+ ```tsx
309
+ // WRONG — importing Route pulls route config into the lazy chunk
310
+ import { Route } from './posts.tsx'
311
+ const data = Route.useLoaderData()
312
+
313
+ // CORRECT — getRouteApi gives typed hooks without pulling in the route
314
+ import { getRouteApi } from '@tanstack/react-router'
315
+ const routeApi = getRouteApi('/posts')
316
+ const data = routeApi.useLoaderData()
317
+ ```
318
+
319
+ ## Cross-References
320
+
321
+ - **router-core/data-loading** — Loader splitting decisions affect data loading performance. Splitting the loader adds latency before data can be fetched.
322
+ - **router-core/type-safety** — `getRouteApi` is the type-safe way to access hooks from split files.