kaddidlehopper 0.1.0
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/CONTEXT.md +139 -0
- package/README.md +47 -0
- package/add-ons/ai/README.md +34 -0
- package/add-ons/ai/assets/_dot_env.local.append +13 -0
- package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
- package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
- package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
- package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
- package/add-ons/ai/assets/src/routes/chat.css +175 -0
- package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
- package/add-ons/ai/info.json +27 -0
- package/add-ons/ai/package.json +17 -0
- package/add-ons/ai/small-logo.svg +8 -0
- package/dist/cli.js +251 -0
- package/dist/index.js +33 -0
- package/dist/types/cli.d.ts +8 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/types.d.ts +14 -0
- package/dist/types.js +1 -0
- package/examples/blog/README.md +60 -0
- package/examples/blog/assets/content/posts/beach.md +12 -0
- package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
- package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
- package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
- package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
- package/examples/blog/assets/content-collections.ts +30 -0
- package/examples/blog/assets/public/beach.jpg +0 -0
- package/examples/blog/assets/public/jungle.jpg +0 -0
- package/examples/blog/assets/public/mountains.jpg +0 -0
- package/examples/blog/assets/public/snorkeling.jpg +0 -0
- package/examples/blog/assets/public/waterfall.jpg +0 -0
- package/examples/blog/assets/src/components/Header.tsx +52 -0
- package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
- package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
- package/examples/blog/assets/src/components/ui/card.tsx +92 -0
- package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
- package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
- package/examples/blog/assets/src/lib/utils.ts +6 -0
- package/examples/blog/assets/src/routes/__root.tsx +57 -0
- package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
- package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
- package/examples/blog/assets/src/routes/index.tsx +19 -0
- package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
- package/examples/blog/assets/src/styles.css +138 -0
- package/examples/blog/info.json +43 -0
- package/examples/blog/package.json +23 -0
- package/examples/events/README.md +110 -0
- package/examples/events/assets/content/speakers/andre-costa.md +22 -0
- package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
- package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
- package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
- package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
- package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
- package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
- package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
- package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
- package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
- package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
- package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
- package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
- package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
- package/examples/events/assets/content-collections.ts +56 -0
- package/examples/events/assets/public/background-1.jpg +0 -0
- package/examples/events/assets/public/background-2.jpg +0 -0
- package/examples/events/assets/public/background-3.jpg +0 -0
- package/examples/events/assets/public/background-4.jpg +0 -0
- package/examples/events/assets/public/conference-logo.png +0 -0
- package/examples/events/assets/public/favicon.ico +0 -0
- package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
- package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
- package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
- package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
- package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
- package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
- package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
- package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
- package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
- package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
- package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
- package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
- package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
- package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
- package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
- package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
- package/examples/events/assets/src/components/Header.tsx +59 -0
- package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
- package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
- package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
- package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
- package/examples/events/assets/src/components/TalkCard.tsx +77 -0
- package/examples/events/assets/src/components/ui/card.tsx +92 -0
- package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
- package/examples/events/assets/src/lib/conference-tools.ts +210 -0
- package/examples/events/assets/src/lib/model-selection.ts +1 -0
- package/examples/events/assets/src/lib/utils.ts +6 -0
- package/examples/events/assets/src/routes/__root.tsx +70 -0
- package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
- package/examples/events/assets/src/routes/index.tsx +192 -0
- package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
- package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
- package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
- package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
- package/examples/events/assets/src/routes/talks.index.tsx +40 -0
- package/examples/events/assets/src/styles.css +182 -0
- package/examples/events/info.json +74 -0
- package/examples/events/package.json +23 -0
- package/examples/marketing/README.md +60 -0
- package/examples/marketing/assets/public/logo.png +0 -0
- package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
- package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
- package/examples/marketing/assets/src/components/Header.tsx +36 -0
- package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
- package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
- package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
- package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
- package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
- package/examples/marketing/assets/src/routes/__root.tsx +57 -0
- package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
- package/examples/marketing/assets/src/routes/index.tsx +72 -0
- package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
- package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
- package/examples/marketing/assets/src/styles.css +212 -0
- package/examples/marketing/info.json +38 -0
- package/examples/marketing/package.json +14 -0
- package/examples/resume/README.md +82 -0
- package/examples/resume/assets/content/education/code-school.md +17 -0
- package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
- package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
- package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
- package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
- package/examples/resume/assets/content-collections.ts +36 -0
- package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
- package/examples/resume/assets/src/components/Header.tsx +33 -0
- package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
- package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
- package/examples/resume/assets/src/components/ui/card.tsx +92 -0
- package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
- package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
- package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
- package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
- package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
- package/examples/resume/assets/src/lib/utils.ts +6 -0
- package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
- package/examples/resume/assets/src/routes/index.tsx +220 -0
- package/examples/resume/assets/src/styles.css +138 -0
- package/examples/resume/info.json +25 -0
- package/examples/resume/package.json +26 -0
- package/package.json +39 -0
- package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
- package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
- package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
- package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
- package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
- package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
- package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
- package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
- package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
- package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
- package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
- package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
- package/project/base/_dot_gitignore +8 -0
- package/project/base/netlify.toml +7 -0
- package/project/base/package.json +33 -0
- package/project/base/public/favicon.ico +0 -0
- package/project/base/public/tanstack-circle-logo.png +0 -0
- package/project/base/public/tanstack-word-logo-white.svg +1 -0
- package/project/base/src/components/Header.tsx +17 -0
- package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
- package/project/base/src/router.tsx +15 -0
- package/project/base/src/routes/__root.tsx +57 -0
- package/project/base/src/routes/index.tsx +48 -0
- package/project/base/src/styles.css +15 -0
- package/project/base/tsconfig.json +28 -0
- package/project/base/vite.config.ts.ejs +25 -0
- package/project/packages.json +22 -0
- package/scripts/check-outdated-packages.js +421 -0
- package/src/cli.ts +343 -0
- package/src/index.ts +49 -0
- package/src/types.ts +15 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tanstack-start-routes
|
|
3
|
+
description: Create and manage routes in TanStack Start using file-based routing. Use when adding new pages, configuring layouts, setting up nested routes, or working with route parameters.
|
|
4
|
+
license: Apache-2.0
|
|
5
|
+
metadata:
|
|
6
|
+
author: tanstack
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# TanStack Start Routes
|
|
11
|
+
|
|
12
|
+
TanStack Start uses file-based routing with TanStack Router. Routes are defined as files in the `src/routes` directory.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
- Adding new pages to your application
|
|
17
|
+
- Setting up nested layouts
|
|
18
|
+
- Configuring route parameters
|
|
19
|
+
- Creating pathless layout routes
|
|
20
|
+
- Organizing route structure
|
|
21
|
+
|
|
22
|
+
## Directory Structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
src/
|
|
26
|
+
├── routes/
|
|
27
|
+
│ ├── __root.tsx # Root layout (required)
|
|
28
|
+
│ ├── index.tsx # Home page (/)
|
|
29
|
+
│ ├── about.tsx # About page (/about)
|
|
30
|
+
│ ├── posts.tsx # Posts layout (/posts)
|
|
31
|
+
│ ├── posts.index.tsx # Posts index (/posts)
|
|
32
|
+
│ ├── posts.$postId.tsx # Dynamic post (/posts/:postId)
|
|
33
|
+
│ ├── _auth.tsx # Pathless layout (no URL segment)
|
|
34
|
+
│ ├── _auth.login.tsx # Login under auth layout (/login)
|
|
35
|
+
│ └── _auth.register.tsx # Register under auth layout (/register)
|
|
36
|
+
├── router.tsx # Router configuration
|
|
37
|
+
└── routeTree.gen.ts # Auto-generated route tree
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Root Route
|
|
41
|
+
|
|
42
|
+
The root route wraps all other routes and defines the document shell:
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// src/routes/__root.tsx
|
|
46
|
+
import {
|
|
47
|
+
Outlet,
|
|
48
|
+
createRootRoute,
|
|
49
|
+
HeadContent,
|
|
50
|
+
Scripts
|
|
51
|
+
} from '@tanstack/react-router';
|
|
52
|
+
import type { ReactNode } from 'react';
|
|
53
|
+
|
|
54
|
+
export const Route = createRootRoute({
|
|
55
|
+
head: () => ({
|
|
56
|
+
meta: [
|
|
57
|
+
{ charSet: 'utf-8' },
|
|
58
|
+
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
59
|
+
{ title: 'My App' },
|
|
60
|
+
],
|
|
61
|
+
}),
|
|
62
|
+
component: RootComponent,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
function RootComponent() {
|
|
66
|
+
return (
|
|
67
|
+
<html>
|
|
68
|
+
<head>
|
|
69
|
+
<HeadContent />
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
<Header />
|
|
73
|
+
<main>
|
|
74
|
+
<Outlet />
|
|
75
|
+
</main>
|
|
76
|
+
<Scripts />
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function Header() {
|
|
83
|
+
return (
|
|
84
|
+
<header>
|
|
85
|
+
<nav>
|
|
86
|
+
<Link to="/">Home</Link>
|
|
87
|
+
<Link to="/about">About</Link>
|
|
88
|
+
<Link to="/posts">Posts</Link>
|
|
89
|
+
</nav>
|
|
90
|
+
</header>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Basic Route
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
// src/routes/about.tsx
|
|
99
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
100
|
+
|
|
101
|
+
export const Route = createFileRoute('/about')({
|
|
102
|
+
component: AboutComponent,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
function AboutComponent() {
|
|
106
|
+
return (
|
|
107
|
+
<div>
|
|
108
|
+
<h1>About Us</h1>
|
|
109
|
+
<p>This is the about page.</p>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Index Route
|
|
116
|
+
|
|
117
|
+
Index routes render when the parent path is matched exactly:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// src/routes/index.tsx (renders at /)
|
|
121
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
122
|
+
|
|
123
|
+
export const Route = createFileRoute('/')({
|
|
124
|
+
component: HomeComponent,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
function HomeComponent() {
|
|
128
|
+
return <h1>Welcome Home</h1>;
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
// src/routes/posts.index.tsx (renders at /posts)
|
|
134
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
135
|
+
|
|
136
|
+
export const Route = createFileRoute('/posts/')({
|
|
137
|
+
component: PostsIndexComponent,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
function PostsIndexComponent() {
|
|
141
|
+
return <h2>Select a post from the list</h2>;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Layout Routes
|
|
146
|
+
|
|
147
|
+
Layout routes wrap child routes with shared UI:
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
// src/routes/posts.tsx
|
|
151
|
+
import { createFileRoute, Outlet, Link } from '@tanstack/react-router';
|
|
152
|
+
|
|
153
|
+
export const Route = createFileRoute('/posts')({
|
|
154
|
+
component: PostsLayoutComponent,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
function PostsLayoutComponent() {
|
|
158
|
+
return (
|
|
159
|
+
<div className="posts-layout">
|
|
160
|
+
<aside>
|
|
161
|
+
<h2>Posts</h2>
|
|
162
|
+
<nav>
|
|
163
|
+
<Link to="/posts/1">Post 1</Link>
|
|
164
|
+
<Link to="/posts/2">Post 2</Link>
|
|
165
|
+
</nav>
|
|
166
|
+
</aside>
|
|
167
|
+
<div className="content">
|
|
168
|
+
<Outlet /> {/* Child routes render here */}
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Dynamic Route Parameters
|
|
176
|
+
|
|
177
|
+
Use `$paramName` for dynamic segments:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
// src/routes/posts.$postId.tsx
|
|
181
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
182
|
+
|
|
183
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
184
|
+
component: PostComponent,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
function PostComponent() {
|
|
188
|
+
const { postId } = Route.useParams();
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<article>
|
|
192
|
+
<h1>Post {postId}</h1>
|
|
193
|
+
</article>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Multiple Parameters
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
// src/routes/users.$userId.posts.$postId.tsx
|
|
202
|
+
// Matches: /users/123/posts/456
|
|
203
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
204
|
+
|
|
205
|
+
export const Route = createFileRoute('/users/$userId/posts/$postId')({
|
|
206
|
+
component: UserPostComponent,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
function UserPostComponent() {
|
|
210
|
+
const { userId, postId } = Route.useParams();
|
|
211
|
+
|
|
212
|
+
return <div>User {userId}'s Post {postId}</div>;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Pathless Layout Routes
|
|
217
|
+
|
|
218
|
+
Prefix with `_` for layouts that don't add URL segments:
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
// src/routes/_auth.tsx
|
|
222
|
+
// This creates a layout but adds no URL segment
|
|
223
|
+
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router';
|
|
224
|
+
|
|
225
|
+
export const Route = createFileRoute('/_auth')({
|
|
226
|
+
beforeLoad: async ({ context }) => {
|
|
227
|
+
// Redirect if already authenticated
|
|
228
|
+
if (context.user) {
|
|
229
|
+
throw redirect({ to: '/dashboard' });
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
component: AuthLayout,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
function AuthLayout() {
|
|
236
|
+
return (
|
|
237
|
+
<div className="auth-container">
|
|
238
|
+
<div className="auth-card">
|
|
239
|
+
<Outlet />
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
// src/routes/_auth.login.tsx
|
|
248
|
+
// Renders at /login (not /_auth/login)
|
|
249
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
250
|
+
|
|
251
|
+
export const Route = createFileRoute('/_auth/login')({
|
|
252
|
+
component: LoginComponent,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
function LoginComponent() {
|
|
256
|
+
return <form>Login Form</form>;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Catch-All (Splat) Routes
|
|
261
|
+
|
|
262
|
+
Use `$` for catch-all routes:
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
// src/routes/files.$.tsx
|
|
266
|
+
// Matches: /files/any/path/here
|
|
267
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
268
|
+
|
|
269
|
+
export const Route = createFileRoute('/files/$')({
|
|
270
|
+
component: FilesComponent,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
function FilesComponent() {
|
|
274
|
+
const { _splat } = Route.useParams();
|
|
275
|
+
// _splat = "any/path/here"
|
|
276
|
+
|
|
277
|
+
return <div>File path: {_splat}</div>;
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## 404 Not Found Route
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
// src/routes/__root.tsx
|
|
285
|
+
import { createRootRoute } from '@tanstack/react-router';
|
|
286
|
+
|
|
287
|
+
export const Route = createRootRoute({
|
|
288
|
+
component: RootComponent,
|
|
289
|
+
notFoundComponent: NotFoundComponent,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
function NotFoundComponent() {
|
|
293
|
+
return (
|
|
294
|
+
<div>
|
|
295
|
+
<h1>404 - Page Not Found</h1>
|
|
296
|
+
<Link to="/">Go Home</Link>
|
|
297
|
+
</div>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Route Configuration Options
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
306
|
+
|
|
307
|
+
export const Route = createFileRoute('/posts/$postId')({
|
|
308
|
+
// Component to render
|
|
309
|
+
component: PostComponent,
|
|
310
|
+
|
|
311
|
+
// Error boundary component
|
|
312
|
+
errorComponent: ErrorComponent,
|
|
313
|
+
|
|
314
|
+
// Loading/pending component
|
|
315
|
+
pendingComponent: LoadingComponent,
|
|
316
|
+
|
|
317
|
+
// Not found component (for this route)
|
|
318
|
+
notFoundComponent: NotFoundComponent,
|
|
319
|
+
|
|
320
|
+
// Validate and parse params
|
|
321
|
+
parseParams: (params) => ({
|
|
322
|
+
postId: parseInt(params.postId, 10),
|
|
323
|
+
}),
|
|
324
|
+
|
|
325
|
+
// Serialize params back to strings
|
|
326
|
+
stringifyParams: (params) => ({
|
|
327
|
+
postId: String(params.postId),
|
|
328
|
+
}),
|
|
329
|
+
|
|
330
|
+
// Validate search params
|
|
331
|
+
validateSearch: (search) => ({
|
|
332
|
+
page: Number(search.page) || 1,
|
|
333
|
+
}),
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## File Naming Conventions
|
|
338
|
+
|
|
339
|
+
| File Name | Route Path |
|
|
340
|
+
|-----------|------------|
|
|
341
|
+
| `index.tsx` | `/` |
|
|
342
|
+
| `about.tsx` | `/about` |
|
|
343
|
+
| `posts.tsx` | `/posts` (layout) |
|
|
344
|
+
| `posts.index.tsx` | `/posts` (index) |
|
|
345
|
+
| `posts.$postId.tsx` | `/posts/:postId` |
|
|
346
|
+
| `posts.$postId.edit.tsx` | `/posts/:postId/edit` |
|
|
347
|
+
| `_layout.tsx` | Pathless layout |
|
|
348
|
+
| `_layout.page.tsx` | `/page` with layout |
|
|
349
|
+
| `files.$.tsx` | `/files/*` (catch-all) |
|
|
350
|
+
|
|
351
|
+
## Directory vs Flat Routes
|
|
352
|
+
|
|
353
|
+
Both styles work and can be mixed:
|
|
354
|
+
|
|
355
|
+
**Flat (dot notation):**
|
|
356
|
+
```
|
|
357
|
+
routes/
|
|
358
|
+
├── posts.tsx
|
|
359
|
+
├── posts.index.tsx
|
|
360
|
+
└── posts.$postId.tsx
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Directory style:**
|
|
364
|
+
```
|
|
365
|
+
routes/
|
|
366
|
+
└── posts/
|
|
367
|
+
├── route.tsx # Layout
|
|
368
|
+
├── index.tsx # Index
|
|
369
|
+
└── $postId.tsx # Dynamic
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Router Configuration
|
|
373
|
+
|
|
374
|
+
```tsx
|
|
375
|
+
// src/router.tsx
|
|
376
|
+
import { createRouter } from '@tanstack/react-router';
|
|
377
|
+
import { routeTree } from './routeTree.gen';
|
|
378
|
+
|
|
379
|
+
export function getRouter() {
|
|
380
|
+
const router = createRouter({
|
|
381
|
+
routeTree,
|
|
382
|
+
scrollRestoration: true,
|
|
383
|
+
defaultPreload: 'intent',
|
|
384
|
+
defaultPreloadStaleTime: 0,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
return router;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
declare module '@tanstack/react-router' {
|
|
391
|
+
interface Register {
|
|
392
|
+
router: ReturnType<typeof getRouter>;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Common Patterns
|
|
398
|
+
|
|
399
|
+
### Protected Routes
|
|
400
|
+
|
|
401
|
+
```tsx
|
|
402
|
+
// src/routes/_protected.tsx
|
|
403
|
+
import { createFileRoute, redirect } from '@tanstack/react-router';
|
|
404
|
+
|
|
405
|
+
export const Route = createFileRoute('/_protected')({
|
|
406
|
+
beforeLoad: async ({ context }) => {
|
|
407
|
+
if (!context.user) {
|
|
408
|
+
throw redirect({
|
|
409
|
+
to: '/login',
|
|
410
|
+
search: { redirect: location.href },
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
component: ProtectedLayout,
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Route Groups (Organization Only)
|
|
419
|
+
|
|
420
|
+
Use parentheses for grouping without affecting URLs:
|
|
421
|
+
|
|
422
|
+
```
|
|
423
|
+
routes/
|
|
424
|
+
├── (marketing)/
|
|
425
|
+
│ ├── about.tsx # /about
|
|
426
|
+
│ └── pricing.tsx # /pricing
|
|
427
|
+
└── (app)/
|
|
428
|
+
├── dashboard.tsx # /dashboard
|
|
429
|
+
└── settings.tsx # /settings
|
|
430
|
+
```
|