@prmichaelsen/acp-visualizer 0.1.0 → 0.1.2

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 (159) hide show
  1. package/package.json +8 -10
  2. package/src/components/ExtraFieldsBadge.tsx +1 -1
  3. package/src/components/FilterBar.tsx +1 -1
  4. package/src/components/Header.tsx +1 -1
  5. package/src/components/MilestoneTable.tsx +1 -1
  6. package/src/components/MilestoneTree.tsx +2 -2
  7. package/src/components/StatusBadge.tsx +1 -1
  8. package/src/components/StatusDot.tsx +1 -1
  9. package/src/components/TaskList.tsx +1 -1
  10. package/src/routes/__root.tsx +5 -5
  11. package/src/routes/api/watch.ts +1 -1
  12. package/src/routes/index.tsx +2 -2
  13. package/src/routes/milestones.tsx +7 -7
  14. package/src/routes/search.tsx +4 -4
  15. package/src/routes/tasks.tsx +3 -3
  16. package/src/services/progress-database.service.ts +3 -3
  17. package/agent/commands/acp.clarification-address.md +0 -417
  18. package/agent/commands/acp.clarification-capture.md +0 -386
  19. package/agent/commands/acp.clarification-create.md +0 -437
  20. package/agent/commands/acp.clarifications-research.md +0 -326
  21. package/agent/commands/acp.command-create.md +0 -432
  22. package/agent/commands/acp.design-create.md +0 -286
  23. package/agent/commands/acp.design-reference.md +0 -355
  24. package/agent/commands/acp.handoff.md +0 -270
  25. package/agent/commands/acp.index.md +0 -423
  26. package/agent/commands/acp.init.md +0 -546
  27. package/agent/commands/acp.package-create.md +0 -895
  28. package/agent/commands/acp.package-info.md +0 -212
  29. package/agent/commands/acp.package-install.md +0 -539
  30. package/agent/commands/acp.package-list.md +0 -280
  31. package/agent/commands/acp.package-publish.md +0 -541
  32. package/agent/commands/acp.package-remove.md +0 -293
  33. package/agent/commands/acp.package-search.md +0 -307
  34. package/agent/commands/acp.package-update.md +0 -361
  35. package/agent/commands/acp.package-validate.md +0 -540
  36. package/agent/commands/acp.pattern-create.md +0 -386
  37. package/agent/commands/acp.plan.md +0 -587
  38. package/agent/commands/acp.proceed.md +0 -882
  39. package/agent/commands/acp.project-create.md +0 -675
  40. package/agent/commands/acp.project-info.md +0 -312
  41. package/agent/commands/acp.project-list.md +0 -226
  42. package/agent/commands/acp.project-remove.md +0 -379
  43. package/agent/commands/acp.project-set.md +0 -227
  44. package/agent/commands/acp.project-update.md +0 -307
  45. package/agent/commands/acp.projects-restore.md +0 -228
  46. package/agent/commands/acp.projects-sync.md +0 -347
  47. package/agent/commands/acp.report.md +0 -407
  48. package/agent/commands/acp.resume.md +0 -239
  49. package/agent/commands/acp.sessions.md +0 -301
  50. package/agent/commands/acp.status.md +0 -293
  51. package/agent/commands/acp.sync.md +0 -364
  52. package/agent/commands/acp.task-create.md +0 -500
  53. package/agent/commands/acp.update.md +0 -302
  54. package/agent/commands/acp.validate.md +0 -466
  55. package/agent/commands/acp.version-check-for-updates.md +0 -276
  56. package/agent/commands/acp.version-check.md +0 -191
  57. package/agent/commands/acp.version-update.md +0 -289
  58. package/agent/commands/command.template.md +0 -339
  59. package/agent/commands/git.commit.md +0 -526
  60. package/agent/commands/git.init.md +0 -514
  61. package/agent/commands/tanstack-cloudflare.deploy.md +0 -272
  62. package/agent/commands/tanstack-cloudflare.tail.md +0 -275
  63. package/agent/design/.gitkeep +0 -0
  64. package/agent/design/design.template.md +0 -154
  65. package/agent/design/local.dashboard-layout-routing.md +0 -288
  66. package/agent/design/local.data-model-yaml-parsing.md +0 -310
  67. package/agent/design/local.search-filtering.md +0 -331
  68. package/agent/design/local.server-api-auto-refresh.md +0 -235
  69. package/agent/design/local.table-tree-views.md +0 -299
  70. package/agent/design/local.visualizer-requirements.md +0 -349
  71. package/agent/design/requirements.template.md +0 -387
  72. package/agent/index/.gitkeep +0 -0
  73. package/agent/index/acp.core.yaml +0 -137
  74. package/agent/index/local.main.template.yaml +0 -37
  75. package/agent/manifest.template.yaml +0 -13
  76. package/agent/manifest.yaml +0 -302
  77. package/agent/milestones/.gitkeep +0 -0
  78. package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +0 -67
  79. package/agent/milestones/milestone-1-{title}.template.md +0 -206
  80. package/agent/milestones/milestone-2-dashboard-views-interaction.md +0 -79
  81. package/agent/package.template.yaml +0 -86
  82. package/agent/patterns/.gitkeep +0 -0
  83. package/agent/patterns/bootstrap.template.md +0 -1237
  84. package/agent/patterns/pattern.template.md +0 -382
  85. package/agent/patterns/tanstack-cloudflare.acl-permissions.md +0 -332
  86. package/agent/patterns/tanstack-cloudflare.action-bar-item.md +0 -416
  87. package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +0 -401
  88. package/agent/patterns/tanstack-cloudflare.auth-session-management.md +0 -387
  89. package/agent/patterns/tanstack-cloudflare.card-and-list.md +0 -271
  90. package/agent/patterns/tanstack-cloudflare.chat-engine.md +0 -353
  91. package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +0 -346
  92. package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +0 -516
  93. package/agent/patterns/tanstack-cloudflare.email-service.md +0 -431
  94. package/agent/patterns/tanstack-cloudflare.expander.md +0 -98
  95. package/agent/patterns/tanstack-cloudflare.fcm-push.md +0 -115
  96. package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +0 -441
  97. package/agent/patterns/tanstack-cloudflare.firebase-auth.md +0 -348
  98. package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +0 -550
  99. package/agent/patterns/tanstack-cloudflare.firebase-storage.md +0 -369
  100. package/agent/patterns/tanstack-cloudflare.form-controls.md +0 -145
  101. package/agent/patterns/tanstack-cloudflare.global-search-context.md +0 -93
  102. package/agent/patterns/tanstack-cloudflare.image-carousel.md +0 -126
  103. package/agent/patterns/tanstack-cloudflare.library-services.md +0 -553
  104. package/agent/patterns/tanstack-cloudflare.lightbox.md +0 -169
  105. package/agent/patterns/tanstack-cloudflare.markdown-content.md +0 -115
  106. package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +0 -98
  107. package/agent/patterns/tanstack-cloudflare.modal.md +0 -156
  108. package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +0 -461
  109. package/agent/patterns/tanstack-cloudflare.notifications-engine.md +0 -151
  110. package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +0 -90
  111. package/agent/patterns/tanstack-cloudflare.og-metadata.md +0 -296
  112. package/agent/patterns/tanstack-cloudflare.pagination.md +0 -442
  113. package/agent/patterns/tanstack-cloudflare.pill-input.md +0 -220
  114. package/agent/patterns/tanstack-cloudflare.provider-adapter.md +0 -401
  115. package/agent/patterns/tanstack-cloudflare.rate-limiting.md +0 -323
  116. package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +0 -338
  117. package/agent/patterns/tanstack-cloudflare.searchable-settings.md +0 -375
  118. package/agent/patterns/tanstack-cloudflare.slide-over.md +0 -129
  119. package/agent/patterns/tanstack-cloudflare.ssr-preload.md +0 -571
  120. package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +0 -508
  121. package/agent/patterns/tanstack-cloudflare.toast-system.md +0 -142
  122. package/agent/patterns/tanstack-cloudflare.unified-header.md +0 -280
  123. package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +0 -628
  124. package/agent/patterns/tanstack-cloudflare.websocket-manager.md +0 -237
  125. package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +0 -358
  126. package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +0 -336
  127. package/agent/progress.template.yaml +0 -161
  128. package/agent/progress.yaml +0 -145
  129. package/agent/schemas/package.schema.yaml +0 -276
  130. package/agent/scripts/acp.common.sh +0 -1781
  131. package/agent/scripts/acp.install.sh +0 -333
  132. package/agent/scripts/acp.package-create.sh +0 -924
  133. package/agent/scripts/acp.package-info.sh +0 -288
  134. package/agent/scripts/acp.package-install.sh +0 -893
  135. package/agent/scripts/acp.package-list.sh +0 -311
  136. package/agent/scripts/acp.package-publish.sh +0 -420
  137. package/agent/scripts/acp.package-remove.sh +0 -348
  138. package/agent/scripts/acp.package-search.sh +0 -156
  139. package/agent/scripts/acp.package-update.sh +0 -517
  140. package/agent/scripts/acp.package-validate.sh +0 -1018
  141. package/agent/scripts/acp.uninstall.sh +0 -85
  142. package/agent/scripts/acp.version-check-for-updates.sh +0 -98
  143. package/agent/scripts/acp.version-check.sh +0 -47
  144. package/agent/scripts/acp.version-update.sh +0 -176
  145. package/agent/scripts/acp.yaml-parser.sh +0 -985
  146. package/agent/scripts/acp.yaml-validate.sh +0 -205
  147. package/agent/tasks/.gitkeep +0 -0
  148. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +0 -210
  149. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +0 -294
  150. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +0 -193
  151. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +0 -262
  152. package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +0 -156
  153. package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +0 -178
  154. package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +0 -141
  155. package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +0 -153
  156. package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +0 -174
  157. package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +0 -233
  158. package/agent/tasks/task-1-{title}.template.md +0 -244
  159. package/vitest.config.ts +0 -27
@@ -1,571 +0,0 @@
1
- # SSR Data Preloading Pattern
2
-
3
- **Category**: Architecture
4
- **Applicable To**: TanStack Start + Cloudflare Workers applications requiring server-side rendering
5
- **Status**: Stable
6
-
7
- ---
8
-
9
- ## Overview
10
-
11
- This pattern demonstrates how to preload data on the server using TanStack Router's `beforeLoad` hook, pass it through route context, and hydrate components with SSR data to eliminate loading flashes. By fetching data server-side before the page renders, you provide instant content to users and search engines while maintaining the ability to add real-time updates via WebSockets.
12
-
13
- The pattern ensures that users never see loading spinners on initial page load, improving perceived performance and providing better SEO since search engines can index the fully-rendered content.
14
-
15
- ---
16
-
17
- ## When to Use This Pattern
18
-
19
- ✅ **Use this pattern when:**
20
- - Building TanStack Start applications with Cloudflare Workers
21
- - Any data can be fetched server-side (Firestore, database, API)
22
- - Working with user-specific data (we use server-side auth, never client-side)
23
- - Need to eliminate loading spinners on initial page load
24
- - Want better SEO (search engines can index the content)
25
- - Improving perceived performance is important
26
- - Working with real-time data (SSR provides initial state, WebSocket adds updates)
27
-
28
- ❌ **Don't use this pattern when:**
29
- - Data is too large (>500KB) and would slow down SSR
30
- - Data fetch is extremely slow (>3 seconds) and would block page load
31
- - Data is purely client-side (localStorage, IndexedDB)
32
- - Building static pages with no dynamic data
33
-
34
- **Note**: We ALWAYS use server-side auth (`getAuthSession`), never client-side. Real-time listeners are attached AFTER SSR hydration.
35
-
36
- ---
37
-
38
- ## Core Principles
39
-
40
- 1. **Server-First Data Loading**: Fetch data on the server before rendering, not after
41
- 2. **beforeLoad Over loader**: Use `beforeLoad` (not `loader`) for SSR data preloading in TanStack Start
42
- 3. **Route Context for Data**: Pass preloaded data through route context, not loader data
43
- 4. **Graceful Degradation**: Handle errors gracefully - don't fail page load if data fetch fails
44
- 5. **Skip Client Fetch**: Components check for SSR data and skip client-side fetch if present
45
- 6. **WebSocket After Hydration**: Real-time listeners attach after SSR hydration completes
46
-
47
- ---
48
-
49
- ## Implementation
50
-
51
- ### Structure
52
-
53
- ```
54
- src/
55
- ├── routes/
56
- │ └── your-route.tsx # Route with beforeLoad
57
- ├── components/
58
- │ └── YourComponent.tsx # Component using initialData
59
- └── services/
60
- └── your-database.service.ts # Database service for SSR
61
- ```
62
-
63
- ### Code Example
64
-
65
- #### Step 1: Route Configuration with beforeLoad
66
-
67
- ```typescript
68
- // src/routes/your-route.tsx
69
- import { createFileRoute, redirect } from '@tanstack/react-router'
70
- import { getAuthSession } from '@/lib/auth/server-fn'
71
- import { YourDatabaseService } from '@/services/your-database.service'
72
- import type { YourDataType } from '@/types/your-types'
73
-
74
- export const Route = createFileRoute('/your-route')({
75
- beforeLoad: async () => {
76
- // 1. Check authentication (if needed)
77
- const user = await getAuthSession()
78
-
79
- if (!user) {
80
- throw redirect({
81
- to: '/auth',
82
- search: { redirect_url: '/your-route' },
83
- })
84
- }
85
-
86
- // 2. Preload data with proper typing
87
- let initialData: YourDataType[] = []
88
-
89
- try {
90
- initialData = await YourDatabaseService.getData(user.uid, 50)
91
- } catch (error) {
92
- console.error('Failed to preload data:', error)
93
- // Continue with empty data - not fatal
94
- }
95
-
96
- // 3. Return data through context
97
- return {
98
- user,
99
- initialData
100
- }
101
- },
102
- component: YourComponent,
103
- })
104
- ```
105
-
106
- #### Step 2: Component Data Access
107
-
108
- ```typescript
109
- // src/routes/your-route.tsx (continued)
110
- function YourComponent() {
111
- // Get data from route context (NOT useLoaderData)
112
- const context = Route.useRouteContext()
113
- const { user, initialData } = context
114
-
115
- return (
116
- <div>
117
- <YourChildComponent initialData={initialData} />
118
- </div>
119
- )
120
- }
121
- ```
122
-
123
- #### Step 3: Child Component Integration
124
-
125
- ```typescript
126
- // src/components/YourChildComponent.tsx
127
- interface YourChildComponentProps {
128
- initialData?: YourDataType[] // SSR data
129
- className?: string
130
- }
131
-
132
- export function YourChildComponent({
133
- initialData = [], // Default to empty array
134
- className
135
- }: YourChildComponentProps) {
136
- const [data, setData] = useState<YourDataType[]>(initialData) // Initialize with SSR data
137
-
138
- useEffect(() => {
139
- // Skip loading if we have SSR data
140
- if (initialData.length > 0) {
141
- console.log('Using SSR data, skipping client fetch')
142
- return
143
- }
144
-
145
- // Only load if no SSR data
146
- loadData()
147
- }, [initialData.length])
148
-
149
- // Component renders immediately with SSR data!
150
- return (
151
- <div className={className}>
152
- {data.map(item => (
153
- <div key={item.id}>{item.name}</div>
154
- ))}
155
- </div>
156
- )
157
- }
158
- ```
159
-
160
- ---
161
-
162
- ## Examples
163
-
164
- ### Example 1: Chat Messages with SSR
165
-
166
- ```typescript
167
- // src/routes/chat.tsx
168
- import { createFileRoute, redirect } from '@tanstack/react-router'
169
- import { getAuthSession } from '@/lib/auth/server-fn'
170
- import { ConversationDatabaseService } from '@/services/conversation-database.service'
171
- import type { Message } from '@/types/chat'
172
-
173
- export const Route = createFileRoute('/chat')({
174
- beforeLoad: async () => {
175
- const user = await getAuthSession()
176
- if (!user) throw redirect({ to: '/auth' })
177
-
178
- const conversationId = 'main'
179
- let initialMessages: Message[] = []
180
-
181
- try {
182
- initialMessages = await ConversationDatabaseService.getMessages(
183
- user.uid,
184
- conversationId,
185
- 50
186
- )
187
- } catch (error) {
188
- console.error('Failed to preload messages:', error)
189
- }
190
-
191
- return { user, conversationId, initialMessages }
192
- },
193
- component: Chat,
194
- })
195
-
196
- function Chat() {
197
- const { user, conversationId, initialMessages } = Route.useRouteContext()
198
-
199
- return (
200
- <ChatInterface
201
- conversationId={conversationId}
202
- initialMessages={initialMessages}
203
- />
204
- )
205
- }
206
- ```
207
-
208
- ### Example 2: WebSocket Integration with SSR
209
-
210
- ```typescript
211
- // src/components/chat/ChatInterface.tsx
212
- interface ChatInterfaceProps {
213
- conversationId?: string
214
- initialMessages?: Message[]
215
- }
216
-
217
- export function ChatInterface({
218
- conversationId,
219
- initialMessages = [],
220
- }: ChatInterfaceProps) {
221
- const [messages, setMessages] = useState<Message[]>(initialMessages)
222
-
223
- useEffect(() => {
224
- const wsClient = new ChatWebSocket({
225
- onConnectionChange: (isConnected) => {
226
- // Skip loading if we have SSR data
227
- if (initialMessages.length > 0) {
228
- console.log('Skipping WebSocket load - using SSR data')
229
- return
230
- }
231
-
232
- // Load data via WebSocket if no SSR data
233
- if (isConnected) {
234
- wsClient.loadMessages(conversationId)
235
- }
236
- },
237
- onMessageReceived: (newMessage) => {
238
- // Add new messages from WebSocket
239
- setMessages(prev => [...prev, newMessage])
240
- }
241
- })
242
-
243
- return () => wsClient.disconnect()
244
- }, [initialMessages.length])
245
-
246
- // Component renders immediately with SSR data!
247
- return (
248
- <div>
249
- {messages.map(msg => (
250
- <div key={msg.id}>{msg.content}</div>
251
- ))}
252
- </div>
253
- )
254
- }
255
- ```
256
-
257
- ### Example 3: User Profile with SSR
258
-
259
- ```typescript
260
- // src/routes/profile.tsx
261
- export const Route = createFileRoute('/profile')({
262
- beforeLoad: async () => {
263
- const user = await getAuthSession()
264
- if (!user) throw redirect({ to: '/auth' })
265
-
266
- let profile = null
267
-
268
- try {
269
- profile = await UserDatabaseService.getProfile(user.uid)
270
- } catch (error) {
271
- console.error('Failed to load profile:', error)
272
- }
273
-
274
- return { user, profile }
275
- },
276
- component: Profile,
277
- })
278
-
279
- function Profile() {
280
- const { user, profile } = Route.useRouteContext()
281
-
282
- return (
283
- <ProfileView initialProfile={profile} />
284
- )
285
- }
286
- ```
287
-
288
- ---
289
-
290
- ## Benefits
291
-
292
- ### 1. Instant Content Display
293
-
294
- Users see content immediately without loading spinners. The page is fully rendered on the server with real data.
295
-
296
- **Before SSR**: Page loads → Show spinner → Fetch data → Render data (2-3 seconds)
297
- **After SSR**: Page loads → Data already rendered (<1 second)
298
-
299
- ### 2. Better SEO
300
-
301
- Search engines can index the fully-rendered content since data is present in the initial HTML.
302
-
303
- ### 3. Improved Perceived Performance
304
-
305
- Users perceive the application as faster because they see content immediately, even if real-time updates take a moment to connect.
306
-
307
- ### 4. Reduced Client-Side Complexity
308
-
309
- Components don't need complex loading states for initial data - they start with data already present.
310
-
311
- ---
312
-
313
- ## Trade-offs
314
-
315
- ### 1. Server-Side Execution Time
316
-
317
- **Downside**: Data fetching happens on the server, which can slow down initial page load if queries are slow.
318
-
319
- **Mitigation**:
320
- - Set timeouts on data fetches (fail gracefully)
321
- - Only preload essential data
322
- - Use caching where appropriate
323
- - Consider skipping SSR for very slow queries
324
-
325
- ### 2. Increased Server Load
326
-
327
- **Downside**: Every page request executes server-side data fetching, increasing server resource usage.
328
-
329
- **Mitigation**:
330
- - Use Cloudflare Workers' edge caching
331
- - Implement query result caching
332
- - Monitor server performance
333
- - Scale horizontally if needed
334
-
335
- ---
336
-
337
- ## Anti-Patterns
338
-
339
- ### ❌ Anti-Pattern 1: Using loader Instead of beforeLoad
340
-
341
- **Description**: Using TanStack Router's `loader` instead of `beforeLoad` for SSR data preloading.
342
-
343
- **Why it's bad**: May not work correctly in TanStack Start setup, inconsistent with project patterns.
344
-
345
- **Instead, do this**: Use `beforeLoad` for SSR data preloading.
346
-
347
- ```typescript
348
- // ❌ Bad: Using loader
349
- export const Route = createFileRoute('/route')({
350
- loader: async () => {
351
- return { data: await fetchData() }
352
- }
353
- })
354
-
355
- // ✅ Good: Using beforeLoad
356
- export const Route = createFileRoute('/route')({
357
- beforeLoad: async () => {
358
- return { data: await fetchData() }
359
- }
360
- })
361
- ```
362
-
363
- ### ❌ Anti-Pattern 2: Forgetting Type Annotation
364
-
365
- **Description**: Not providing explicit type annotation for initialData variable.
366
-
367
- **Why it's bad**: TypeScript can't infer the type, leading to `any[]` and loss of type safety.
368
-
369
- **Instead, do this**: Always provide explicit type annotation.
370
-
371
- ```typescript
372
- // ❌ Bad: Implicit any[]
373
- let initialData = []
374
-
375
- // ✅ Good: Explicit type
376
- let initialData: Message[] = []
377
- ```
378
-
379
- ### ❌ Anti-Pattern 3: Always Fetching on Client
380
-
381
- **Description**: Component always fetches data on mount, ignoring SSR data.
382
-
383
- **Why it's bad**: Wastes the SSR data, causes unnecessary re-renders, shows loading state unnecessarily.
384
-
385
- **Instead, do this**: Check for SSR data first.
386
-
387
- ```typescript
388
- // ❌ Bad: Always fetching
389
- useEffect(() => {
390
- loadData() // Always runs, ignores SSR data
391
- }, [])
392
-
393
- // ✅ Good: Check for SSR data
394
- useEffect(() => {
395
- if (initialData.length > 0) return // Skip if SSR data exists
396
- loadData()
397
- }, [initialData.length])
398
- ```
399
-
400
- ### ❌ Anti-Pattern 4: Failing Page Load on Data Error
401
-
402
- **Description**: Throwing errors in `beforeLoad` when data fetch fails.
403
-
404
- **Why it's bad**: Prevents page from loading at all, poor user experience.
405
-
406
- **Instead, do this**: Handle errors gracefully and continue with empty data.
407
-
408
- ```typescript
409
- // ❌ Bad: Throwing on error
410
- beforeLoad: async () => {
411
- const data = await fetchData() // Throws if fails
412
- return { data }
413
- }
414
-
415
- // ✅ Good: Graceful error handling
416
- beforeLoad: async () => {
417
- let data = []
418
- try {
419
- data = await fetchData()
420
- } catch (error) {
421
- console.error('Failed to preload:', error)
422
- // Continue with empty data
423
- }
424
- return { data }
425
- }
426
- ```
427
-
428
- ---
429
-
430
- ## Testing Strategy
431
-
432
- ### Unit Testing Components with SSR Data
433
-
434
- ```typescript
435
- import { render, screen } from '@testing-library/react'
436
- import { YourComponent } from './YourComponent'
437
-
438
- describe('YourComponent with SSR', () => {
439
- it('should render with SSR data', () => {
440
- const initialData = [
441
- { id: '1', name: 'Item 1' },
442
- { id: '2', name: 'Item 2' },
443
- ]
444
-
445
- render(<YourComponent initialData={initialData} />)
446
-
447
- expect(screen.getByText('Item 1')).toBeInTheDocument()
448
- expect(screen.getByText('Item 2')).toBeInTheDocument()
449
- })
450
-
451
- it('should not fetch data when SSR data provided', () => {
452
- const loadDataSpy = jest.fn()
453
- const initialData = [{ id: '1', name: 'Item 1' }]
454
-
455
- render(<YourComponent initialData={initialData} />)
456
-
457
- expect(loadDataSpy).not.toHaveBeenCalled()
458
- })
459
- })
460
- ```
461
-
462
- ### Testing SSR with JavaScript Disabled
463
-
464
- 1. Disable JavaScript in browser
465
- 2. Navigate to route
466
- 3. Data should still render (proves SSR works)
467
-
468
- ### Testing Hydration
469
-
470
- 1. Enable JavaScript
471
- 2. Check console for "Using SSR data" log
472
- 3. Verify no loading spinner appears
473
- 4. Verify no duplicate data fetches
474
-
475
- ---
476
-
477
- ## Related Patterns
478
-
479
- - **[Library Services Pattern](./tanstack-cloudflare.library-services.md)**: Database services are used in `beforeLoad` for server-side data fetching
480
- - **[User-Scoped Collections](./tanstack-cloudflare.user-scoped-collections.md)**: SSR preloading works with user-scoped Firestore collections
481
-
482
- ---
483
-
484
- ## Migration Guide
485
-
486
- ### Step 1: Identify Client-Side Data Fetching
487
-
488
- Find components that fetch data in `useEffect`:
489
-
490
- ```typescript
491
- // Current pattern
492
- useEffect(() => {
493
- fetchData().then(setData)
494
- }, [])
495
- ```
496
-
497
- ### Step 2: Add beforeLoad to Route
498
-
499
- ```typescript
500
- // Add to route file
501
- export const Route = createFileRoute('/route')({
502
- beforeLoad: async () => {
503
- let initialData = []
504
- try {
505
- initialData = await YourDatabaseService.getData()
506
- } catch (error) {
507
- console.error('Preload failed:', error)
508
- }
509
- return { initialData }
510
- },
511
- component: YourComponent,
512
- })
513
- ```
514
-
515
- ### Step 3: Update Component to Accept initialData
516
-
517
- ```typescript
518
- // Update component props
519
- interface Props {
520
- initialData?: DataType[]
521
- }
522
-
523
- export function YourComponent({ initialData = [] }: Props) {
524
- const [data, setData] = useState(initialData)
525
-
526
- useEffect(() => {
527
- if (initialData.length > 0) return
528
- fetchData().then(setData)
529
- }, [initialData.length])
530
- }
531
- ```
532
-
533
- ### Step 4: Pass Data from Route
534
-
535
- ```typescript
536
- function YourComponent() {
537
- const { initialData } = Route.useRouteContext()
538
- return <YourChildComponent initialData={initialData} />
539
- }
540
- ```
541
-
542
- ---
543
-
544
- ## References
545
-
546
- - [TanStack Router Documentation](https://tanstack.com/router/latest)
547
- - [TanStack Start Documentation](https://tanstack.com/start/latest)
548
- - [Cloudflare Workers](https://developers.cloudflare.com/workers/)
549
- - [Server-Side Rendering Best Practices](https://web.dev/rendering-on-the-web/)
550
-
551
- ---
552
-
553
- ## Checklist for Implementation
554
-
555
- - [ ] Use `beforeLoad` (not `loader`)
556
- - [ ] Add proper TypeScript types for initialData
557
- - [ ] Handle errors gracefully (don't fail page load)
558
- - [ ] Pass data through route context
559
- - [ ] Access with `Route.useRouteContext()`
560
- - [ ] Initialize component state with SSR data
561
- - [ ] Skip client-side fetch if SSR data exists
562
- - [ ] Test with JavaScript disabled
563
- - [ ] Test hydration (no flash/re-render)
564
- - [ ] Test real-time updates still work
565
-
566
- ---
567
-
568
- **Status**: Stable - Proven pattern for TanStack Start + Cloudflare Workers
569
- **Recommendation**: Use for all routes that display user-specific data
570
- **Last Updated**: 2026-02-21
571
- **Contributors**: Patrick Michaelsen