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.
Files changed (183) hide show
  1. package/CONTEXT.md +139 -0
  2. package/README.md +47 -0
  3. package/add-ons/ai/README.md +34 -0
  4. package/add-ons/ai/assets/_dot_env.local.append +13 -0
  5. package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
  6. package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
  7. package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
  8. package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
  9. package/add-ons/ai/assets/src/routes/chat.css +175 -0
  10. package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
  11. package/add-ons/ai/info.json +27 -0
  12. package/add-ons/ai/package.json +17 -0
  13. package/add-ons/ai/small-logo.svg +8 -0
  14. package/dist/cli.js +251 -0
  15. package/dist/index.js +33 -0
  16. package/dist/types/cli.d.ts +8 -0
  17. package/dist/types/index.d.ts +2 -0
  18. package/dist/types/types.d.ts +14 -0
  19. package/dist/types.js +1 -0
  20. package/examples/blog/README.md +60 -0
  21. package/examples/blog/assets/content/posts/beach.md +12 -0
  22. package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
  23. package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
  24. package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
  25. package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
  26. package/examples/blog/assets/content-collections.ts +30 -0
  27. package/examples/blog/assets/public/beach.jpg +0 -0
  28. package/examples/blog/assets/public/jungle.jpg +0 -0
  29. package/examples/blog/assets/public/mountains.jpg +0 -0
  30. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  31. package/examples/blog/assets/public/waterfall.jpg +0 -0
  32. package/examples/blog/assets/src/components/Header.tsx +52 -0
  33. package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
  34. package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
  35. package/examples/blog/assets/src/components/ui/card.tsx +92 -0
  36. package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
  37. package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
  38. package/examples/blog/assets/src/lib/utils.ts +6 -0
  39. package/examples/blog/assets/src/routes/__root.tsx +57 -0
  40. package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
  41. package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
  42. package/examples/blog/assets/src/routes/index.tsx +19 -0
  43. package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
  44. package/examples/blog/assets/src/styles.css +138 -0
  45. package/examples/blog/info.json +43 -0
  46. package/examples/blog/package.json +23 -0
  47. package/examples/events/README.md +110 -0
  48. package/examples/events/assets/content/speakers/andre-costa.md +22 -0
  49. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
  50. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
  51. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
  52. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
  53. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
  54. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
  55. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
  56. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
  57. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
  58. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
  59. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
  60. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
  61. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
  62. package/examples/events/assets/content-collections.ts +56 -0
  63. package/examples/events/assets/public/background-1.jpg +0 -0
  64. package/examples/events/assets/public/background-2.jpg +0 -0
  65. package/examples/events/assets/public/background-3.jpg +0 -0
  66. package/examples/events/assets/public/background-4.jpg +0 -0
  67. package/examples/events/assets/public/conference-logo.png +0 -0
  68. package/examples/events/assets/public/favicon.ico +0 -0
  69. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  70. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  71. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  72. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  73. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  74. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  75. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  76. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  77. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  78. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  79. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  80. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  81. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  82. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  83. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  84. package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
  85. package/examples/events/assets/src/components/Header.tsx +59 -0
  86. package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
  87. package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
  88. package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
  89. package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
  90. package/examples/events/assets/src/components/TalkCard.tsx +77 -0
  91. package/examples/events/assets/src/components/ui/card.tsx +92 -0
  92. package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
  93. package/examples/events/assets/src/lib/conference-tools.ts +210 -0
  94. package/examples/events/assets/src/lib/model-selection.ts +1 -0
  95. package/examples/events/assets/src/lib/utils.ts +6 -0
  96. package/examples/events/assets/src/routes/__root.tsx +70 -0
  97. package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
  98. package/examples/events/assets/src/routes/index.tsx +192 -0
  99. package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
  100. package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
  101. package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
  102. package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
  103. package/examples/events/assets/src/routes/talks.index.tsx +40 -0
  104. package/examples/events/assets/src/styles.css +182 -0
  105. package/examples/events/info.json +74 -0
  106. package/examples/events/package.json +23 -0
  107. package/examples/marketing/README.md +60 -0
  108. package/examples/marketing/assets/public/logo.png +0 -0
  109. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  110. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  111. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  112. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  113. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  114. package/examples/marketing/assets/src/components/Header.tsx +36 -0
  115. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
  116. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
  117. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
  118. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
  119. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
  120. package/examples/marketing/assets/src/routes/__root.tsx +57 -0
  121. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
  122. package/examples/marketing/assets/src/routes/index.tsx +72 -0
  123. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
  124. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
  125. package/examples/marketing/assets/src/styles.css +212 -0
  126. package/examples/marketing/info.json +38 -0
  127. package/examples/marketing/package.json +14 -0
  128. package/examples/resume/README.md +82 -0
  129. package/examples/resume/assets/content/education/code-school.md +17 -0
  130. package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
  131. package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
  132. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
  133. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
  134. package/examples/resume/assets/content-collections.ts +36 -0
  135. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  136. package/examples/resume/assets/src/components/Header.tsx +33 -0
  137. package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
  138. package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
  139. package/examples/resume/assets/src/components/ui/card.tsx +92 -0
  140. package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
  141. package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
  142. package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
  143. package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
  144. package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
  145. package/examples/resume/assets/src/lib/utils.ts +6 -0
  146. package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
  147. package/examples/resume/assets/src/routes/index.tsx +220 -0
  148. package/examples/resume/assets/src/styles.css +138 -0
  149. package/examples/resume/info.json +25 -0
  150. package/examples/resume/package.json +26 -0
  151. package/package.json +39 -0
  152. package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
  153. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
  154. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
  155. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
  156. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
  157. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
  158. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
  159. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
  160. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
  161. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
  162. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
  163. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
  164. package/project/base/_dot_gitignore +8 -0
  165. package/project/base/netlify.toml +7 -0
  166. package/project/base/package.json +33 -0
  167. package/project/base/public/favicon.ico +0 -0
  168. package/project/base/public/tanstack-circle-logo.png +0 -0
  169. package/project/base/public/tanstack-word-logo-white.svg +1 -0
  170. package/project/base/src/components/Header.tsx +17 -0
  171. package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
  172. package/project/base/src/router.tsx +15 -0
  173. package/project/base/src/routes/__root.tsx +57 -0
  174. package/project/base/src/routes/index.tsx +48 -0
  175. package/project/base/src/styles.css +15 -0
  176. package/project/base/tsconfig.json +28 -0
  177. package/project/base/vite.config.ts.ejs +25 -0
  178. package/project/packages.json +22 -0
  179. package/scripts/check-outdated-packages.js +421 -0
  180. package/src/cli.ts +343 -0
  181. package/src/index.ts +49 -0
  182. package/src/types.ts +15 -0
  183. package/tsconfig.json +17 -0
@@ -0,0 +1,426 @@
1
+ ---
2
+ name: tanstack-start-loaders
3
+ description: Load data for TanStack Start routes using beforeLoad and loader functions. Use when fetching data for pages, implementing route guards, or setting up route context. IMPORTANT - Loaders should call server functions for data access.
4
+ license: Apache-2.0
5
+ metadata:
6
+ author: tanstack
7
+ version: "1.0"
8
+ ---
9
+
10
+ # TanStack Start Loaders
11
+
12
+ TanStack Start provides `beforeLoad` and `loader` functions for route data loading. Both are **isomorphic** - they run on the server during SSR and on the client during navigation.
13
+
14
+ ## Critical Rule
15
+
16
+ **Loaders should call server functions when accessing databases, APIs with secrets, or any server-only resources.** Loaders are isomorphic (run on both server and client), so they cannot directly access server-only code.
17
+
18
+ ```tsx
19
+ // ❌ WRONG - Direct database access in loader
20
+ export const Route = createFileRoute('/posts')({
21
+ loader: async () => {
22
+ // This will FAIL on client-side navigation!
23
+ const posts = await db.query('SELECT * FROM posts');
24
+ return { posts };
25
+ },
26
+ });
27
+
28
+ // ✅ CORRECT - Call server function from loader
29
+ import { getPosts } from '../server/posts.functions';
30
+
31
+ export const Route = createFileRoute('/posts')({
32
+ loader: async () => {
33
+ const posts = await getPosts();
34
+ return { posts };
35
+ },
36
+ });
37
+ ```
38
+
39
+ ## When to Use
40
+
41
+ - **beforeLoad**: Route guards, authentication, setting context
42
+ - **loader**: Fetching data for the route component
43
+
44
+ ## beforeLoad vs loader
45
+
46
+ | Feature | beforeLoad | loader |
47
+ |---------|------------|--------|
48
+ | Execution | Sequential (parent → child) | Parallel across routes |
49
+ | Return | Merges into context | Route-specific data |
50
+ | Use case | Guards, auth, context setup | Data fetching |
51
+
52
+ ## beforeLoad Function
53
+
54
+ Runs sequentially from parent to child. Use for guards and context:
55
+
56
+ ```tsx
57
+ // src/routes/_protected.tsx
58
+ import { createFileRoute, redirect } from '@tanstack/react-router';
59
+ import { getUser } from '../server/auth.functions';
60
+
61
+ export const Route = createFileRoute('/_protected')({
62
+ beforeLoad: async ({ context }) => {
63
+ // Call server function - NOT direct database access
64
+ const user = await getUser();
65
+
66
+ if (!user) {
67
+ throw redirect({
68
+ to: '/login',
69
+ search: { redirect: location.href },
70
+ });
71
+ }
72
+
73
+ // Return value merges into context for child routes
74
+ return { user };
75
+ },
76
+ component: ProtectedLayout,
77
+ });
78
+ ```
79
+
80
+ Child routes can access parent's beforeLoad data:
81
+
82
+ ```tsx
83
+ // src/routes/_protected.dashboard.tsx
84
+ import { createFileRoute } from '@tanstack/react-router';
85
+
86
+ export const Route = createFileRoute('/_protected/dashboard')({
87
+ beforeLoad: ({ context }) => {
88
+ // Access user from parent's beforeLoad
89
+ console.log('User:', context.user);
90
+ },
91
+ component: DashboardComponent,
92
+ });
93
+ ```
94
+
95
+ ## loader Function
96
+
97
+ Fetches route-specific data. Runs in parallel across matched routes:
98
+
99
+ ```tsx
100
+ // src/routes/posts.tsx
101
+ import { createFileRoute } from '@tanstack/react-router';
102
+ import { getPosts } from '../server/posts.functions';
103
+
104
+ export const Route = createFileRoute('/posts')({
105
+ loader: async () => {
106
+ // Call server function for data
107
+ const posts = await getPosts();
108
+ return { posts };
109
+ },
110
+ component: PostsComponent,
111
+ });
112
+
113
+ function PostsComponent() {
114
+ // Type-safe access to loader data
115
+ const { posts } = Route.useLoaderData();
116
+
117
+ return (
118
+ <ul>
119
+ {posts.map((post) => (
120
+ <li key={post.id}>{post.title}</li>
121
+ ))}
122
+ </ul>
123
+ );
124
+ }
125
+ ```
126
+
127
+ ## Loader with Parameters
128
+
129
+ ```tsx
130
+ // src/routes/posts.$postId.tsx
131
+ import { createFileRoute } from '@tanstack/react-router';
132
+ import { getPost } from '../server/posts.functions';
133
+
134
+ export const Route = createFileRoute('/posts/$postId')({
135
+ loader: async ({ params }) => {
136
+ const post = await getPost({ data: { id: params.postId } });
137
+
138
+ if (!post) {
139
+ throw new Error('Post not found');
140
+ }
141
+
142
+ return { post };
143
+ },
144
+ component: PostComponent,
145
+ });
146
+
147
+ function PostComponent() {
148
+ const { post } = Route.useLoaderData();
149
+
150
+ return (
151
+ <article>
152
+ <h1>{post.title}</h1>
153
+ <p>{post.content}</p>
154
+ </article>
155
+ );
156
+ }
157
+ ```
158
+
159
+ ## Loader Dependencies (loaderDeps)
160
+
161
+ Re-run loader when search params change:
162
+
163
+ ```tsx
164
+ // src/routes/posts.tsx
165
+ import { createFileRoute } from '@tanstack/react-router';
166
+ import { searchPosts } from '../server/posts.functions';
167
+
168
+ export const Route = createFileRoute('/posts')({
169
+ validateSearch: (search) => ({
170
+ page: Number(search.page) || 1,
171
+ filter: search.filter as string | undefined,
172
+ }),
173
+
174
+ // Define which values trigger loader re-runs
175
+ loaderDeps: ({ search }) => ({
176
+ page: search.page,
177
+ filter: search.filter,
178
+ }),
179
+
180
+ loader: async ({ deps }) => {
181
+ // deps contains values from loaderDeps
182
+ const { posts, total } = await searchPosts({
183
+ data: { page: deps.page, filter: deps.filter }
184
+ });
185
+ return { posts, total, page: deps.page };
186
+ },
187
+
188
+ component: PostsComponent,
189
+ });
190
+
191
+ function PostsComponent() {
192
+ const { posts, total, page } = Route.useLoaderData();
193
+ const navigate = useNavigate();
194
+
195
+ return (
196
+ <div>
197
+ <ul>
198
+ {posts.map((post) => (
199
+ <li key={post.id}>{post.title}</li>
200
+ ))}
201
+ </ul>
202
+
203
+ <button
204
+ onClick={() => navigate({ search: { page: page + 1 } })}
205
+ >
206
+ Next Page
207
+ </button>
208
+ </div>
209
+ );
210
+ }
211
+ ```
212
+
213
+ ## Deferred Data Loading
214
+
215
+ Load critical data first, stream non-critical data:
216
+
217
+ ```tsx
218
+ // src/routes/posts.$postId.tsx
219
+ import { createFileRoute, Await } from '@tanstack/react-router';
220
+ import { getPost, getComments, getRelatedPosts } from '../server/posts.functions';
221
+
222
+ export const Route = createFileRoute('/posts/$postId')({
223
+ loader: async ({ params }) => {
224
+ // Critical data - await it
225
+ const post = await getPost({ data: { id: params.postId } });
226
+
227
+ // Non-critical data - don't await, stream later
228
+ const commentsPromise = getComments({ data: { postId: params.postId } });
229
+ const relatedPromise = getRelatedPosts({ data: { postId: params.postId } });
230
+
231
+ return {
232
+ post,
233
+ comments: commentsPromise, // Promise, not resolved
234
+ related: relatedPromise, // Promise, not resolved
235
+ };
236
+ },
237
+ component: PostComponent,
238
+ });
239
+
240
+ function PostComponent() {
241
+ const { post, comments, related } = Route.useLoaderData();
242
+
243
+ return (
244
+ <article>
245
+ <h1>{post.title}</h1>
246
+ <p>{post.content}</p>
247
+
248
+ {/* Comments stream in when ready */}
249
+ <Suspense fallback={<p>Loading comments...</p>}>
250
+ <Await promise={comments}>
251
+ {(resolvedComments) => (
252
+ <section>
253
+ <h2>Comments</h2>
254
+ {resolvedComments.map((c) => (
255
+ <div key={c.id}>{c.text}</div>
256
+ ))}
257
+ </section>
258
+ )}
259
+ </Await>
260
+ </Suspense>
261
+
262
+ {/* Related posts stream in when ready */}
263
+ <Suspense fallback={<p>Loading related...</p>}>
264
+ <Await promise={related}>
265
+ {(resolvedRelated) => (
266
+ <aside>
267
+ <h2>Related Posts</h2>
268
+ <ul>
269
+ {resolvedRelated.map((p) => (
270
+ <li key={p.id}>{p.title}</li>
271
+ ))}
272
+ </ul>
273
+ </aside>
274
+ )}
275
+ </Await>
276
+ </Suspense>
277
+ </article>
278
+ );
279
+ }
280
+ ```
281
+
282
+ ## Context from Router
283
+
284
+ Pass initial context from router creation:
285
+
286
+ ```tsx
287
+ // src/router.tsx
288
+ import { createRouter } from '@tanstack/react-router';
289
+ import { routeTree } from './routeTree.gen';
290
+
291
+ export function getRouter() {
292
+ return createRouter({
293
+ routeTree,
294
+ context: {
295
+ // Initial context available to all routes
296
+ queryClient: new QueryClient(),
297
+ },
298
+ });
299
+ }
300
+ ```
301
+
302
+ Access in loaders:
303
+
304
+ ```tsx
305
+ export const Route = createFileRoute('/posts')({
306
+ loader: async ({ context }) => {
307
+ // Access router context
308
+ return context.queryClient.ensureQueryData(postsQueryOptions());
309
+ },
310
+ });
311
+ ```
312
+
313
+ ## Error Handling
314
+
315
+ ```tsx
316
+ export const Route = createFileRoute('/posts/$postId')({
317
+ loader: async ({ params }) => {
318
+ const post = await getPost({ data: { id: params.postId } });
319
+
320
+ if (!post) {
321
+ throw new Error('Post not found');
322
+ }
323
+
324
+ return { post };
325
+ },
326
+
327
+ // Custom error component
328
+ errorComponent: ({ error }) => (
329
+ <div>
330
+ <h1>Error Loading Post</h1>
331
+ <p>{error.message}</p>
332
+ <Link to="/posts">Back to Posts</Link>
333
+ </div>
334
+ ),
335
+
336
+ component: PostComponent,
337
+ });
338
+ ```
339
+
340
+ ## Pending/Loading States
341
+
342
+ ```tsx
343
+ export const Route = createFileRoute('/posts')({
344
+ loader: async () => {
345
+ const posts = await getPosts();
346
+ return { posts };
347
+ },
348
+
349
+ // Show while loading
350
+ pendingComponent: () => <div>Loading posts...</div>,
351
+
352
+ // Minimum time to show pending (prevents flash)
353
+ pendingMinMs: 200,
354
+
355
+ // Delay before showing pending
356
+ pendingMs: 100,
357
+
358
+ component: PostsComponent,
359
+ });
360
+ ```
361
+
362
+ ## Stale Time Configuration
363
+
364
+ Control when loaders re-run:
365
+
366
+ ```tsx
367
+ export const Route = createFileRoute('/posts')({
368
+ // Data is fresh for 5 minutes
369
+ staleTime: 5 * 60 * 1000,
370
+
371
+ // Preload data is fresh for 30 seconds
372
+ preloadStaleTime: 30 * 1000,
373
+
374
+ // Garbage collect after 10 minutes
375
+ gcTime: 10 * 60 * 1000,
376
+
377
+ loader: async () => {
378
+ return { posts: await getPosts() };
379
+ },
380
+ });
381
+ ```
382
+
383
+ ## Using with TanStack Query
384
+
385
+ For complex caching needs, use TanStack Query with loaders:
386
+
387
+ ```tsx
388
+ import { createFileRoute } from '@tanstack/react-router';
389
+ import { useSuspenseQuery, queryOptions } from '@tanstack/react-query';
390
+ import { getPosts } from '../server/posts.functions';
391
+
392
+ const postsQueryOptions = () => queryOptions({
393
+ queryKey: ['posts'],
394
+ queryFn: () => getPosts(),
395
+ });
396
+
397
+ export const Route = createFileRoute('/posts')({
398
+ loader: async ({ context }) => {
399
+ // Ensure data is in cache before rendering
400
+ await context.queryClient.ensureQueryData(postsQueryOptions());
401
+ },
402
+ component: PostsComponent,
403
+ });
404
+
405
+ function PostsComponent() {
406
+ // Use query hook for reactive updates
407
+ const { data: posts } = useSuspenseQuery(postsQueryOptions());
408
+
409
+ return (
410
+ <ul>
411
+ {posts.map((post) => (
412
+ <li key={post.id}>{post.title}</li>
413
+ ))}
414
+ </ul>
415
+ );
416
+ }
417
+ ```
418
+
419
+ ## Summary
420
+
421
+ 1. **Always use server functions** in loaders for server-only operations
422
+ 2. **beforeLoad** for guards and context (sequential)
423
+ 3. **loader** for data fetching (parallel)
424
+ 4. Use **loaderDeps** to re-run on search param changes
425
+ 5. **Defer** non-critical data with promises
426
+ 6. Configure **staleTime** for caching behavior