@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.
- package/bin/intent.js +25 -0
- package/dist/cjs/load-matches.cjs +4 -1
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/router.cjs +2 -1
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/esm/load-matches.js +4 -1
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/router.js +2 -1
- package/dist/esm/router.js.map +1 -1
- package/package.json +9 -2
- package/skills/router-core/SKILL.md +139 -0
- package/skills/router-core/auth-and-guards/SKILL.md +458 -0
- package/skills/router-core/code-splitting/SKILL.md +322 -0
- package/skills/router-core/data-loading/SKILL.md +485 -0
- package/skills/router-core/navigation/SKILL.md +448 -0
- package/skills/router-core/not-found-and-errors/SKILL.md +435 -0
- package/skills/router-core/path-params/SKILL.md +382 -0
- package/skills/router-core/search-params/SKILL.md +355 -0
- package/skills/router-core/search-params/references/validation-patterns.md +379 -0
- package/skills/router-core/ssr/SKILL.md +437 -0
- package/skills/router-core/type-safety/SKILL.md +497 -0
- package/src/load-matches.ts +4 -1
- package/src/router.ts +2 -1
|
@@ -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.
|