@tanstack/cta-framework-react-cra 0.34.13 → 0.34.15

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,7 @@
1
+ import * as Sentry from '@sentry/tanstackstart-react'
2
+ Sentry.init({
3
+ dsn: import.meta.env.VITE_SENTRY_DSN,
4
+ // Adds request headers and IP for users, for more info visit:
5
+ // https://docs.sentry.io/platforms/javascript/guides/tanstackstart-react/configuration/options/#sendDefaultPii
6
+ sendDefaultPii: true,
7
+ })
@@ -9,11 +9,17 @@
9
9
  import * as fs from 'node:fs/promises'
10
10
  import { createFileRoute } from '@tanstack/react-router'
11
11
  import { createServerFn } from '@tanstack/react-start'
12
- import * as Sentry from "@sentry/tanstackstart-react";
12
+ import * as Sentry from '@sentry/tanstackstart-react'
13
13
  import { useState, useEffect, useRef } from 'react'
14
14
 
15
15
  export const Route = createFileRoute('/demo/sentry/testing')({
16
16
  component: RouteComponent,
17
+ errorComponent: ({ error }) => {
18
+ useEffect(() => {
19
+ Sentry.captureException(error)
20
+ }, [error])
21
+ return <div>Error: {error.message}</div>
22
+ },
17
23
  })
18
24
 
19
25
  // Server function that will error
@@ -21,9 +27,9 @@ const badServerFunc = createServerFn({
21
27
  method: 'GET',
22
28
  }).handler(async () => {
23
29
  return await Sentry.startSpan(
24
- {
30
+ {
25
31
  name: 'Reading non-existent file',
26
- op: 'file.read'
32
+ op: 'file.read',
27
33
  },
28
34
  async () => {
29
35
  try {
@@ -33,7 +39,7 @@ const badServerFunc = createServerFn({
33
39
  Sentry.captureException(error)
34
40
  throw error
35
41
  }
36
- }
42
+ },
37
43
  )
38
44
  })
39
45
 
@@ -42,14 +48,14 @@ const goodServerFunc = createServerFn({
42
48
  method: 'GET',
43
49
  }).handler(async () => {
44
50
  return await Sentry.startSpan(
45
- {
51
+ {
46
52
  name: 'Successful server operation',
47
- op: 'demo.success'
53
+ op: 'demo.success',
48
54
  },
49
55
  async () => {
50
- await new Promise(resolve => setTimeout(resolve, 500))
56
+ await new Promise((resolve) => setTimeout(resolve, 500))
51
57
  return { success: true }
52
- }
58
+ },
53
59
  )
54
60
  })
55
61
 
@@ -62,127 +68,134 @@ function RouteComponent() {
62
68
  const [replayEvents, setReplayEvents] = useState<string[]>([])
63
69
  const [copiedSpan, setCopiedSpan] = useState<string | null>(null)
64
70
  const startTimeRef = useRef<string>('')
65
-
71
+
66
72
  useEffect(() => {
67
73
  // Set initial timestamp only once on client
68
74
  if (!startTimeRef.current) {
69
75
  startTimeRef.current = new Date().toISOString()
70
76
  }
71
-
77
+
72
78
  if (demoStep > 0) {
73
- const secondsElapsed = ((new Date().getTime() - new Date(startTimeRef.current).getTime()) / 1000).toFixed(1)
74
- setReplayEvents(prev => [...prev, `Step ${demoStep}: +${secondsElapsed}s`])
79
+ const secondsElapsed = (
80
+ (new Date().getTime() - new Date(startTimeRef.current).getTime()) /
81
+ 1000
82
+ ).toFixed(1)
83
+ setReplayEvents((prev) => [
84
+ ...prev,
85
+ `Step ${demoStep}: +${secondsElapsed}s`,
86
+ ])
75
87
  }
76
88
  }, [demoStep])
77
89
 
78
90
  const handleCopy = (operation: string) => {
79
- navigator.clipboard.writeText(operation);
80
- setCopiedSpan(operation);
81
- setTimeout(() => setCopiedSpan(null), 2000);
91
+ navigator.clipboard.writeText(operation)
92
+ setCopiedSpan(operation)
93
+ setTimeout(() => setCopiedSpan(null), 2000)
82
94
  }
83
95
 
84
96
  const handleClientError = async () => {
85
- setIsLoading(prev => ({ ...prev, clientError: true }))
86
- setHasError(prev => ({ ...prev, clientError: false }))
87
- setShowTrace(prev => ({ ...prev, clientError: true }))
88
-
97
+ setIsLoading((prev) => ({ ...prev, clientError: true }))
98
+ setHasError((prev) => ({ ...prev, clientError: false }))
99
+ setShowTrace((prev) => ({ ...prev, clientError: true }))
100
+
89
101
  try {
90
102
  await Sentry.startSpan(
91
- {
103
+ {
92
104
  name: 'Client Error Flow Demo',
93
105
  op: 'demo.client-error-flow',
94
- },
106
+ },
95
107
  async () => {
96
- Sentry.setContext("demo", {
97
- feature: "client-error-demo",
98
- triggered_at: new Date().toISOString()
99
- });
100
-
108
+ Sentry.setContext('demo', {
109
+ feature: 'client-error-demo',
110
+ triggered_at: new Date().toISOString(),
111
+ })
112
+
101
113
  // Simulate a client-side error
102
114
  throw new Error('Client-side error demonstration')
103
- }
115
+ },
104
116
  )
105
117
  } catch (error) {
106
- setHasError(prev => ({ ...prev, clientError: true }))
107
- setSpanOps(prev => ({ ...prev, clientError: 'demo.client-error-flow' }))
118
+ setHasError((prev) => ({ ...prev, clientError: true }))
119
+ setSpanOps((prev) => ({ ...prev, clientError: 'demo.client-error-flow' }))
108
120
  Sentry.captureException(error)
109
121
  } finally {
110
- setIsLoading(prev => ({ ...prev, clientError: false }))
122
+ setIsLoading((prev) => ({ ...prev, clientError: false }))
111
123
  }
112
124
  }
113
125
 
114
126
  const handleServerError = async () => {
115
- setIsLoading(prev => ({ ...prev, serverError: true }))
116
- setHasError(prev => ({ ...prev, serverError: false }))
117
- setShowTrace(prev => ({ ...prev, serverError: true }))
118
-
127
+ setIsLoading((prev) => ({ ...prev, serverError: true }))
128
+ setHasError((prev) => ({ ...prev, serverError: false }))
129
+ setShowTrace((prev) => ({ ...prev, serverError: true }))
130
+
119
131
  try {
120
132
  await Sentry.startSpan(
121
- {
133
+ {
122
134
  name: 'Server Error Flow Demo',
123
135
  op: 'demo.server-error-flow',
124
- },
136
+ },
125
137
  async () => {
126
- Sentry.setContext("demo", {
127
- feature: "server-error-demo",
128
- triggered_at: new Date().toISOString()
129
- });
130
-
138
+ Sentry.setContext('demo', {
139
+ feature: 'server-error-demo',
140
+ triggered_at: new Date().toISOString(),
141
+ })
142
+
131
143
  await badServerFunc()
132
- }
144
+ },
133
145
  )
134
146
  } catch (error) {
135
- setHasError(prev => ({ ...prev, serverError: true }))
136
- setSpanOps(prev => ({ ...prev, serverError: 'demo.server-error-flow' }))
147
+ setHasError((prev) => ({ ...prev, serverError: true }))
148
+ setSpanOps((prev) => ({ ...prev, serverError: 'demo.server-error-flow' }))
137
149
  Sentry.captureException(error)
138
150
  } finally {
139
- setIsLoading(prev => ({ ...prev, serverError: false }))
151
+ setIsLoading((prev) => ({ ...prev, serverError: false }))
140
152
  }
141
153
  }
142
154
 
143
155
  const handleClientTrace = async () => {
144
- setIsLoading(prev => ({ ...prev, client: true }))
145
- setShowTrace(prev => ({ ...prev, client: true }))
146
-
156
+ setIsLoading((prev) => ({ ...prev, client: true }))
157
+ setShowTrace((prev) => ({ ...prev, client: true }))
158
+
147
159
  await Sentry.startSpan(
148
- {
160
+ {
149
161
  name: 'Client Operation',
150
162
  op: 'demo.client',
151
- },
163
+ },
152
164
  async () => {
153
165
  // Simulate some client-side work
154
- await new Promise(resolve => setTimeout(resolve, 1000))
155
- }
166
+ await new Promise((resolve) => setTimeout(resolve, 1000))
167
+ },
156
168
  )
157
-
158
- setSpanOps(prev => ({ ...prev, client: 'demo.client' }))
159
- setIsLoading(prev => ({ ...prev, client: false }))
169
+
170
+ setSpanOps((prev) => ({ ...prev, client: 'demo.client' }))
171
+ setIsLoading((prev) => ({ ...prev, client: false }))
160
172
  }
161
173
 
162
174
  const handleServerTrace = async () => {
163
- setIsLoading(prev => ({ ...prev, server: true }))
164
- setShowTrace(prev => ({ ...prev, server: true }))
165
-
175
+ setIsLoading((prev) => ({ ...prev, server: true }))
176
+ setShowTrace((prev) => ({ ...prev, server: true }))
177
+
166
178
  try {
167
179
  await Sentry.startSpan(
168
- {
180
+ {
169
181
  name: 'Server Operation',
170
182
  op: 'demo.server',
171
- },
183
+ },
172
184
  async () => {
173
185
  await goodServerFunc()
174
- }
186
+ },
175
187
  )
176
- setSpanOps(prev => ({ ...prev, server: 'demo.server' }))
188
+ setSpanOps((prev) => ({ ...prev, server: 'demo.server' }))
177
189
  } finally {
178
- setIsLoading(prev => ({ ...prev, server: false }))
190
+ setIsLoading((prev) => ({ ...prev, server: false }))
179
191
  }
180
192
  }
181
193
 
182
194
  return (
183
195
  <>
184
- <style dangerouslySetInnerHTML={{
185
- __html: `
196
+ <style
197
+ dangerouslySetInnerHTML={{
198
+ __html: `
186
199
  @keyframes fadeOut {
187
200
  from { opacity: 1; transform: translateY(0); }
188
201
  to { opacity: 0; transform: translateY(-10px); }
@@ -190,19 +203,23 @@ function RouteComponent() {
190
203
  .animate-fade-out {
191
204
  animation: fadeOut 2s ease-out forwards;
192
205
  }
193
- `
194
- }} />
195
- <div className="min-h-[calc(100vh-32px)] text-white p-8" style={{
196
- backgroundImage: 'radial-gradient(41.11% 49.93% at 50% 49.93%, #8d5494 0%, #563275 52.26%, #1f1633 100%)'
197
- }}>
206
+ `,
207
+ }}
208
+ />
209
+ <div
210
+ className="min-h-[calc(100vh-32px)] text-white p-8"
211
+ style={{
212
+ backgroundImage:
213
+ 'radial-gradient(41.11% 49.93% at 50% 49.93%, #8d5494 0%, #563275 52.26%, #1f1633 100%)',
214
+ }}
215
+ >
198
216
  <div className="max-w-7xl mx-auto">
199
217
  {/* Header */}
200
218
  <div className="text-center mb-12">
201
- <h1 className="text-8xl font-bold mb-4 text-white">
202
- Sentry
203
- </h1>
219
+ <h1 className="text-8xl font-bold mb-4 text-white">Sentry</h1>
204
220
  <p className="text-4xl font-semibold text-white">
205
- Code <span className="inline-block -rotate-9">breaks</span>, fix it faster
221
+ Code <span className="inline-block -rotate-9">breaks</span>, fix
222
+ it faster
206
223
  </p>
207
224
  </div>
208
225
 
@@ -212,24 +229,35 @@ function RouteComponent() {
212
229
  <div className="bg-[#1C2333] rounded-lg border border-gray-800 p-6">
213
230
  <div className="space-y-4 text-gray-300">
214
231
  <p>
215
- The Sentry integration monitors this application across all routes; not just this one (we care about all tabs) using our <code>@sentry/react</code> and <code>@sentry/node</code> packages.
232
+ The Sentry integration monitors this application across all
233
+ routes; not just this one (we care about all tabs) using our{' '}
234
+ <code>@sentry/react</code> and <code>@sentry/node</code>{' '}
235
+ packages.
216
236
  </p>
217
237
  <div className="grid grid-cols-4 gap-4">
218
238
  <div className="bg-[#2D3555] rounded-lg p-4 border border-gray-700 hover:border-purple-500/50 transition-colors">
219
239
  <div className="font-bold mb-1">Error Monitoring</div>
220
- <div className="text-sm text-gray-400">across client side and server functions</div>
240
+ <div className="text-sm text-gray-400">
241
+ across client side and server functions
242
+ </div>
221
243
  </div>
222
244
  <div className="bg-[#2D3555] rounded-lg p-4 border border-gray-700 hover:border-purple-500/50 transition-colors">
223
245
  <div className="font-bold mb-1">Tracing and Spans</div>
224
- <div className="text-sm text-gray-400">for client and server side performance</div>
246
+ <div className="text-sm text-gray-400">
247
+ for client and server side performance
248
+ </div>
225
249
  </div>
226
250
  <div className="bg-[#2D3555] rounded-lg p-4 border border-gray-700 hover:border-purple-500/50 transition-colors">
227
251
  <div className="font-bold mb-1">Session replay</div>
228
- <div className="text-sm text-gray-400">real user session playback</div>
252
+ <div className="text-sm text-gray-400">
253
+ real user session playback
254
+ </div>
229
255
  </div>
230
256
  <div className="bg-[#2D3555] rounded-lg p-4 border border-gray-700 hover:border-purple-500/50 transition-colors">
231
257
  <div className="font-bold mb-1">Real-time alerts</div>
232
- <div className="text-sm text-gray-400">because sleep is overrated anyway</div>
258
+ <div className="text-sm text-gray-400">
259
+ because sleep is overrated anyway
260
+ </div>
233
261
  </div>
234
262
  </div>
235
263
  </div>
@@ -239,26 +267,31 @@ function RouteComponent() {
239
267
  <div className="grid grid-cols-2 gap-8">
240
268
  {/* Client Side Testing */}
241
269
  <div className="bg-[#1C2333] rounded-lg p-6 border border-gray-800">
242
- <h2 className="text-xl font-semibold text-white mb-6">Client-Side Testing</h2>
270
+ <h2 className="text-xl font-semibold text-white mb-6">
271
+ Client-Side Testing
272
+ </h2>
243
273
  <div className="space-y-6">
244
274
  <div>
245
275
  <button
246
276
  type="button"
247
277
  onClick={() => {
248
- setDemoStep(prev => prev + 1)
278
+ setDemoStep((prev) => prev + 1)
249
279
  handleClientError()
250
280
  }}
251
281
  className="w-full text-white rounded-md p-4 relative overflow-hidden group"
252
282
  style={{
253
- background: 'linear-gradient(120deg, #c83852, #b44092 25%, #6a5fc1 50%, #452650 55%, #452650)',
283
+ background:
284
+ 'linear-gradient(120deg, #c83852, #b44092 25%, #6a5fc1 50%, #452650 55%, #452650)',
254
285
  backgroundPosition: '2% 0',
255
- backgroundSize: '250% 100%'
286
+ backgroundSize: '250% 100%',
256
287
  }}
257
288
  >
258
289
  <div className="absolute inset-0 bg-gradient-to-r from-red-500/10 to-orange-500/10 opacity-0 group-hover:opacity-100 transition-opacity" />
259
290
  <div className="relative">
260
291
  <div className="flex items-center mb-2">
261
- <span className="font-medium">Trigger Client-Side Error</span>
292
+ <span className="font-medium">
293
+ Trigger Client-Side Error
294
+ </span>
262
295
  </div>
263
296
  </div>
264
297
  </button>
@@ -266,9 +299,17 @@ function RouteComponent() {
266
299
  <div className="mt-4 space-y-2">
267
300
  <div className="bg-red-900/20 border border-red-500/50 rounded-lg p-2">
268
301
  <div className="flex items-center text-red-400 text-sm">
269
- <svg className="w-4 h-4 mr-2" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" stroke="currentColor">
270
- <title>Red Warning Sign</title>
271
- <path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
302
+ <svg
303
+ className="w-4 h-4 mr-2"
304
+ fill="none"
305
+ strokeLinecap="round"
306
+ strokeLinejoin="round"
307
+ strokeWidth="2"
308
+ viewBox="0 0 24 24"
309
+ stroke="currentColor"
310
+ >
311
+ <title>Red Warning Sign</title>
312
+ <path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
272
313
  </svg>
273
314
  Client-side error captured and traced
274
315
  </div>
@@ -282,8 +323,12 @@ function RouteComponent() {
282
323
  onClick={() => handleCopy(spanOps.clientError)}
283
324
  title="Click to copy operation name"
284
325
  >
285
- <span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
286
- <code className="text-purple-400 text-sm font-mono">{spanOps.clientError}</code>
326
+ <span className="text-purple-300 text-sm font-medium mr-2">
327
+ span.op
328
+ </span>
329
+ <code className="text-purple-400 text-sm font-mono">
330
+ {spanOps.clientError}
331
+ </code>
287
332
  </button>
288
333
  {copiedSpan === spanOps.clientError && (
289
334
  <div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
@@ -301,32 +346,39 @@ function RouteComponent() {
301
346
  <button
302
347
  type="button"
303
348
  onClick={() => {
304
- setDemoStep(prev => prev + 1)
349
+ setDemoStep((prev) => prev + 1)
305
350
  handleClientTrace()
306
351
  }}
307
352
  className="w-full text-white rounded-md p-4 relative overflow-hidden group"
308
353
  style={{
309
- background: 'linear-gradient(120deg, #c83852, #b44092 25%, #6a5fc1 50%, #452650 55%, #452650)',
354
+ background:
355
+ 'linear-gradient(120deg, #c83852, #b44092 25%, #6a5fc1 50%, #452650 55%, #452650)',
310
356
  backgroundPosition: '2% 0',
311
- backgroundSize: '250% 100%'
357
+ backgroundSize: '250% 100%',
312
358
  }}
313
359
  >
314
360
  <div className="absolute inset-0 bg-gradient-to-r from-blue-500/10 to-purple-500/10 opacity-0 group-hover:opacity-100 transition-opacity" />
315
361
  <div className="relative">
316
362
  <div className="flex items-center mb-2">
317
- <span className="font-medium">Test Client-Side Span</span>
363
+ <span className="font-medium">
364
+ Test Client-Side Span
365
+ </span>
318
366
  </div>
319
367
  </div>
320
368
  </button>
321
369
  {showTrace.client && (
322
370
  <div className="mt-4 space-y-2">
323
371
  <div className="flex items-center">
324
- <div className={`w-3 h-3 rounded-full ${isLoading.client ? 'bg-blue-500 animate-pulse' : 'bg-green-500'}`} />
372
+ <div
373
+ className={`w-3 h-3 rounded-full ${isLoading.client ? 'bg-blue-500 animate-pulse' : 'bg-green-500'}`}
374
+ />
325
375
  <div className="ml-2 flex-1">
326
376
  <div className="h-1.5 bg-[#2D3555] rounded">
327
- <div
377
+ <div
328
378
  className="h-full bg-blue-500 rounded transition-all duration-500"
329
- style={{ width: isLoading.client ? '60%' : '100%' }}
379
+ style={{
380
+ width: isLoading.client ? '60%' : '100%',
381
+ }}
330
382
  />
331
383
  </div>
332
384
  </div>
@@ -341,8 +393,12 @@ function RouteComponent() {
341
393
  onClick={() => handleCopy(spanOps.client)}
342
394
  title="Click to copy operation name"
343
395
  >
344
- <span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
345
- <code className="text-purple-400 text-sm font-mono">{spanOps.client}</code>
396
+ <span className="text-purple-300 text-sm font-medium mr-2">
397
+ span.op
398
+ </span>
399
+ <code className="text-purple-400 text-sm font-mono">
400
+ {spanOps.client}
401
+ </code>
346
402
  </button>
347
403
  {copiedSpan === spanOps.client && (
348
404
  <div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
@@ -361,26 +417,31 @@ function RouteComponent() {
361
417
 
362
418
  {/* Server Side Testing */}
363
419
  <div className="bg-[#1C2333] rounded-lg p-6 border border-gray-800">
364
- <h2 className="text-xl font-semibold text-white mb-6">Server-Side Testing</h2>
420
+ <h2 className="text-xl font-semibold text-white mb-6">
421
+ Server-Side Testing
422
+ </h2>
365
423
  <div className="space-y-6">
366
424
  <div>
367
425
  <button
368
426
  type="button"
369
427
  onClick={() => {
370
- setDemoStep(prev => prev + 1)
428
+ setDemoStep((prev) => prev + 1)
371
429
  handleServerError()
372
430
  }}
373
431
  className="w-full text-white rounded-md p-4 relative overflow-hidden group"
374
432
  style={{
375
- background: 'linear-gradient(120deg, #c83852, #b44092 25%, #6a5fc1 50%, #452650 55%, #452650)',
433
+ background:
434
+ 'linear-gradient(120deg, #c83852, #b44092 25%, #6a5fc1 50%, #452650 55%, #452650)',
376
435
  backgroundPosition: '2% 0',
377
- backgroundSize: '250% 100%'
436
+ backgroundSize: '250% 100%',
378
437
  }}
379
438
  >
380
439
  <div className="absolute inset-0 bg-gradient-to-r from-red-500/10 to-orange-500/10 opacity-0 group-hover:opacity-100 transition-opacity" />
381
440
  <div className="relative">
382
441
  <div className="flex items-center mb-2">
383
- <span className="font-medium">Trigger Server Error</span>
442
+ <span className="font-medium">
443
+ Trigger Server Error
444
+ </span>
384
445
  </div>
385
446
  </div>
386
447
  </button>
@@ -388,7 +449,15 @@ function RouteComponent() {
388
449
  <div className="mt-4 space-y-2">
389
450
  <div className="bg-red-900/20 border border-red-500/50 rounded-lg p-3">
390
451
  <div className="flex items-center text-red-400 text-sm">
391
- <svg className="w-4 h-4 mr-2" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" stroke="currentColor">
452
+ <svg
453
+ className="w-4 h-4 mr-2"
454
+ fill="none"
455
+ strokeLinecap="round"
456
+ strokeLinejoin="round"
457
+ strokeWidth="2"
458
+ viewBox="0 0 24 24"
459
+ stroke="currentColor"
460
+ >
392
461
  <title>Red Warning Sign</title>
393
462
  <path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
394
463
  </svg>
@@ -398,14 +467,18 @@ function RouteComponent() {
398
467
  <div className="bg-purple-900/20 border border-purple-500/50 rounded-lg p-3">
399
468
  <div className="flex items-center justify-between">
400
469
  <div className="relative">
401
- <button
470
+ <button
402
471
  type="button"
403
472
  className={`inline-flex items-center bg-purple-900/40 px-3 py-1.5 rounded-lg border border-purple-500/50 cursor-pointer hover:bg-purple-900/60 transition-all ${copiedSpan === spanOps.serverError ? 'scale-95' : ''}`}
404
473
  onClick={() => handleCopy(spanOps.serverError)}
405
474
  title="Click to copy operation name"
406
475
  >
407
- <span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
408
- <code className="text-purple-400 text-sm font-mono">{spanOps.serverError}</code>
476
+ <span className="text-purple-300 text-sm font-medium mr-2">
477
+ span.op
478
+ </span>
479
+ <code className="text-purple-400 text-sm font-mono">
480
+ {spanOps.serverError}
481
+ </code>
409
482
  </button>
410
483
  {copiedSpan === spanOps.serverError && (
411
484
  <div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
@@ -423,14 +496,15 @@ function RouteComponent() {
423
496
  <button
424
497
  type="button"
425
498
  onClick={() => {
426
- setDemoStep(prev => prev + 1)
499
+ setDemoStep((prev) => prev + 1)
427
500
  handleServerTrace()
428
501
  }}
429
502
  className="w-full text-white rounded-md p-4 relative overflow-hidden group"
430
503
  style={{
431
- background: 'linear-gradient(120deg, #c83852, #b44092 25%, #6a5fc1 50%, #452650 55%, #452650)',
504
+ background:
505
+ 'linear-gradient(120deg, #c83852, #b44092 25%, #6a5fc1 50%, #452650 55%, #452650)',
432
506
  backgroundPosition: '2% 0',
433
- backgroundSize: '250% 100%'
507
+ backgroundSize: '250% 100%',
434
508
  }}
435
509
  >
436
510
  <div className="absolute inset-0 bg-gradient-to-r from-blue-500/10 to-purple-500/10 opacity-0 group-hover:opacity-100 transition-opacity" />
@@ -443,12 +517,16 @@ function RouteComponent() {
443
517
  {showTrace.server && (
444
518
  <div className="mt-4 space-y-2">
445
519
  <div className="flex items-center">
446
- <div className={`w-3 h-3 rounded-full ${isLoading.server ? 'bg-purple-500 animate-pulse' : 'bg-green-500'}`} />
520
+ <div
521
+ className={`w-3 h-3 rounded-full ${isLoading.server ? 'bg-purple-500 animate-pulse' : 'bg-green-500'}`}
522
+ />
447
523
  <div className="ml-2 flex-1">
448
524
  <div className="h-1.5 bg-[#2D3555] rounded">
449
- <div
525
+ <div
450
526
  className="h-full bg-purple-500 rounded transition-all duration-500"
451
- style={{ width: isLoading.server ? '60%' : '100%' }}
527
+ style={{
528
+ width: isLoading.server ? '60%' : '100%',
529
+ }}
452
530
  />
453
531
  </div>
454
532
  </div>
@@ -457,14 +535,18 @@ function RouteComponent() {
457
535
  <div className="bg-purple-900/20 border border-purple-500/50 rounded-lg p-3">
458
536
  <div className="flex items-center justify-between">
459
537
  <div className="relative">
460
- <button
538
+ <button
461
539
  type="button"
462
540
  className={`inline-flex items-center bg-purple-900/40 px-3 py-1.5 rounded-lg border border-purple-500/50 cursor-pointer hover:bg-purple-900/60 transition-all ${copiedSpan === spanOps.server ? 'scale-95' : ''}`}
463
541
  onClick={() => handleCopy(spanOps.server)}
464
542
  title="Click to copy operation name"
465
543
  >
466
- <span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
467
- <code className="text-purple-400 text-sm font-mono">{spanOps.server}</code>
544
+ <span className="text-purple-300 text-sm font-medium mr-2">
545
+ span.op
546
+ </span>
547
+ <code className="text-purple-400 text-sm font-mono">
548
+ {spanOps.server}
549
+ </code>
468
550
  </button>
469
551
  {copiedSpan === spanOps.server && (
470
552
  <div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
@@ -1,5 +1,10 @@
1
1
  {
2
+ "scripts": {
3
+ "build": "vite build && cp instrument.server.mjs .output/server",
4
+ "dev": "NODE_OPTIONS='--import ./instrument.server.mjs' vite dev --port 3000",
5
+ "start": "node --import ./.output/server/instrument.server.mjs .output/server/index.mjs"
6
+ },
2
7
  "dependencies": {
3
- "@sentry/tanstackstart-react": "^10.17.0"
8
+ "@sentry/tanstackstart-react": "^10.22.0"
4
9
  }
5
10
  }
@@ -2,13 +2,16 @@ import { createRouter } from '@tanstack/react-router'<% if (addOnEnabled['tansta
2
2
  import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
3
3
  import * as TanstackQuery from "./integrations/tanstack-query/root-provider";
4
4
  <% } %>
5
+ <% if (addOnEnabled.sentry) { %>
6
+ import * as Sentry from "@sentry/tanstackstart-react";
7
+ <% } %>
5
8
 
6
9
  // Import the generated route tree
7
10
  import { routeTree } from './routeTree.gen'
8
11
 
9
12
  // Create a new router instance
10
13
  export const getRouter = () => {
11
- <% if (addOnEnabled['tanstack-query']) { %>
14
+ <% if (addOnEnabled['tanstack-query']) { %>
12
15
  const rqContext = TanstackQuery.getContext();
13
16
 
14
17
  const router = createRouter({
@@ -25,14 +28,22 @@ export const getRouter = () => {
25
28
  })
26
29
 
27
30
  setupRouterSsrQueryIntegration({router, queryClient: rqContext.queryClient})
28
-
29
- return router;
30
-
31
- <% } else { %>
32
- return createRouter({
31
+ <% } else { %>
32
+ const router = createRouter({
33
33
  routeTree,
34
34
  scrollRestoration: true,
35
35
  defaultPreloadStaleTime: 0,
36
36
  })
37
- <% } %>
37
+ <% } %>
38
+
39
+ <% if (addOnEnabled.sentry) { %>
40
+ if (!router.isServer) {
41
+ Sentry.init({
42
+ dsn: import.meta.env.VITE_SENTRY_DSN,
43
+ integrations: [],
44
+ });
45
+ }
46
+ <% } %>
47
+
48
+ return router;
38
49
  }
@@ -3,8 +3,6 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite';
3
3
  import viteReact from '@vitejs/plugin-react'
4
4
  import viteTsConfigPaths from 'vite-tsconfig-paths'<% if (tailwind) { %>
5
5
  import tailwindcss from "@tailwindcss/vite"
6
- <% } %><% if (addOnEnabled.sentry) { %>
7
- import { wrapVinxiConfigWithSentry } from "@sentry/tanstackstart-react";
8
6
  <% } %><% for(const integration of integrations.filter(i => i.type === 'vite-plugin')) { %><%- integrationImportContent(integration) %>
9
7
  <% } %>
10
8
 
@@ -24,14 +22,4 @@ const config = defineConfig({
24
22
  ],
25
23
  })
26
24
 
27
- <% if (addOnEnabled.sentry) { %>
28
- export default wrapVinxiConfigWithSentry(config, {
29
- org: process.env.VITE_SENTRY_ORG,
30
- project: process.env.VITE_SENTRY_PROJECT,
31
- authToken: process.env.SENTRY_AUTH_TOKEN,
32
- // Only print logs for uploading source maps in CI
33
- // Set to `true` to suppress logs
34
- silent: !process.env.CI,
35
- });<% } else { %>
36
25
  export default config
37
- <% } %>
package/dist/checksum.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
2
  // Generated from add-ons, examples, hosts, project, and toolchains directories
3
- export const contentChecksum = '9955097b6723f7743c47d4ab8baa3100fddb88f3ab2f53c82666573b02934250';
3
+ export const contentChecksum = 'b8aed4bd3da4fd99c4df983c4bf13aaeabc3f044035f5b2849a4075ccb244ce6';
@@ -1 +1 @@
1
- export declare const contentChecksum = "9955097b6723f7743c47d4ab8baa3100fddb88f3ab2f53c82666573b02934250";
1
+ export declare const contentChecksum = "b8aed4bd3da4fd99c4df983c4bf13aaeabc3f044035f5b2849a4075ccb244ce6";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/cta-framework-react-cra",
3
- "version": "0.34.13",
3
+ "version": "0.34.15",
4
4
  "description": "CTA Framework for React (Create React App)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -23,7 +23,7 @@
23
23
  "author": "Jack Herrington <jherr@pobox.com>",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
- "@tanstack/cta-engine": "0.34.11"
26
+ "@tanstack/cta-engine": "0.34.15"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "^24.6.0",
package/src/checksum.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
2
  // Generated from add-ons, examples, hosts, project, and toolchains directories
3
- export const contentChecksum = '9955097b6723f7743c47d4ab8baa3100fddb88f3ab2f53c82666573b02934250'
3
+ export const contentChecksum = 'b8aed4bd3da4fd99c4df983c4bf13aaeabc3f044035f5b2849a4075ccb244ce6'
@@ -7,7 +7,7 @@
7
7
  "/public/robots.txt": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\nDisallow:\n",
8
8
  "/src/components/Header.tsx": "import { Link } from '@tanstack/react-router'\n\nimport { useState } from 'react'\nimport {\n ChevronDown,\n ChevronRight,\n Home,\n Menu,\n Network,\n SquareFunction,\n StickyNote,\n X,\n} from 'lucide-react'\n\nexport default function Header() {\n const [isOpen, setIsOpen] = useState(false)\n const [groupedExpanded, setGroupedExpanded] = useState<\n Record<string, boolean>\n >({})\n\n return (\n <>\n <header className=\"p-4 flex items-center bg-gray-800 text-white shadow-lg\">\n <button\n onClick={() => setIsOpen(true)}\n className=\"p-2 hover:bg-gray-700 rounded-lg transition-colors\"\n aria-label=\"Open menu\"\n >\n <Menu size={24} />\n </button>\n <h1 className=\"ml-4 text-xl font-semibold\">\n <Link to=\"/\">\n <img\n src=\"/tanstack-word-logo-white.svg\"\n alt=\"TanStack Logo\"\n className=\"h-10\"\n />\n </Link>\n </h1>\n </header>\n\n <aside\n className={`fixed top-0 left-0 h-full w-80 bg-gray-900 text-white shadow-2xl z-50 transform transition-transform duration-300 ease-in-out flex flex-col ${\n isOpen ? 'translate-x-0' : '-translate-x-full'\n }`}\n >\n <div className=\"flex items-center justify-between p-4 border-b border-gray-700\">\n <h2 className=\"text-xl font-bold\">Navigation</h2>\n <button\n onClick={() => setIsOpen(false)}\n className=\"p-2 hover:bg-gray-800 rounded-lg transition-colors\"\n aria-label=\"Close menu\"\n >\n <X size={24} />\n </button>\n </div>\n\n <nav className=\"flex-1 p-4 overflow-y-auto\">\n <Link\n to=\"/\"\n onClick={() => setIsOpen(false)}\n className=\"flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2\"\n activeProps={{\n className:\n 'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',\n }}\n >\n <Home size={20} />\n <span className=\"font-medium\">Home</span>\n </Link>\n\n {/* Demo Links Start */}\n\n <Link\n to=\"/demo/start/server-funcs\"\n onClick={() => setIsOpen(false)}\n className=\"flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2\"\n activeProps={{\n className:\n 'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',\n }}\n >\n <SquareFunction size={20} />\n <span className=\"font-medium\">Start - Server Functions</span>\n </Link>\n\n <Link\n to=\"/demo/start/api-request\"\n onClick={() => setIsOpen(false)}\n className=\"flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2\"\n activeProps={{\n className:\n 'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',\n }}\n >\n <Network size={20} />\n <span className=\"font-medium\">Start - API Request</span>\n </Link>\n\n <div className=\"flex flex-row justify-between\">\n <Link\n to=\"/demo/start/ssr\"\n onClick={() => setIsOpen(false)}\n className=\"flex-1 flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2\"\n activeProps={{\n className:\n 'flex-1 flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',\n }}\n >\n <StickyNote size={20} />\n <span className=\"font-medium\">Start - SSR Demos</span>\n </Link>\n <button\n className=\"p-2 hover:bg-gray-800 rounded-lg transition-colors\"\n onClick={() =>\n setGroupedExpanded((prev) => ({\n ...prev,\n StartSSRDemo: !prev.StartSSRDemo,\n }))\n }\n >\n {groupedExpanded.StartSSRDemo ? (\n <ChevronDown size={20} />\n ) : (\n <ChevronRight size={20} />\n )}\n </button>\n </div>\n {groupedExpanded.StartSSRDemo && (\n <div className=\"flex flex-col ml-4\">\n <Link\n to=\"/demo/start/ssr/spa-mode\"\n onClick={() => setIsOpen(false)}\n className=\"flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2\"\n activeProps={{\n className:\n 'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',\n }}\n >\n <StickyNote size={20} />\n <span className=\"font-medium\">SPA Mode</span>\n </Link>\n\n <Link\n to=\"/demo/start/ssr/full-ssr\"\n onClick={() => setIsOpen(false)}\n className=\"flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2\"\n activeProps={{\n className:\n 'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',\n }}\n >\n <StickyNote size={20} />\n <span className=\"font-medium\">Full SSR</span>\n </Link>\n\n <Link\n to=\"/demo/start/ssr/data-only\"\n onClick={() => setIsOpen(false)}\n className=\"flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2\"\n activeProps={{\n className:\n 'flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2',\n }}\n >\n <StickyNote size={20} />\n <span className=\"font-medium\">Data Only</span>\n </Link>\n </div>\n )}\n\n {/* Demo Links End */}\n </nav>\n </aside>\n </>\n )\n}\n",
9
9
  "/src/data/demo.punk-songs.ts": "import { createServerFn } from '@tanstack/react-start'\n\nexport const getPunkSongs = createServerFn({\n method: 'GET',\n}).handler(async () => [\n { id: 1, name: 'Teenage Dirtbag', artist: 'Wheatus' },\n { id: 2, name: 'Smells Like Teen Spirit', artist: 'Nirvana' },\n { id: 3, name: 'The Middle', artist: 'Jimmy Eat World' },\n { id: 4, name: 'My Own Worst Enemy', artist: 'Lit' },\n { id: 5, name: 'Fat Lip', artist: 'Sum 41' },\n { id: 6, name: 'All the Small Things', artist: 'blink-182' },\n { id: 7, name: 'Beverly Hills', artist: 'Weezer' },\n])\n",
10
- "/src/router.tsx": "import { createRouter } from '@tanstack/react-router'\n\n// Import the generated route tree\nimport { routeTree } from './routeTree.gen'\n\n// Create a new router instance\nexport const getRouter = () => {\n return createRouter({\n routeTree,\n scrollRestoration: true,\n defaultPreloadStaleTime: 0,\n })\n}\n",
10
+ "/src/router.tsx": "import { createRouter } from '@tanstack/react-router'\n\n// Import the generated route tree\nimport { routeTree } from './routeTree.gen'\n\n// Create a new router instance\nexport const getRouter = () => {\n const router = createRouter({\n routeTree,\n scrollRestoration: true,\n defaultPreloadStaleTime: 0,\n })\n\n return router\n}\n",
11
11
  "/src/routes/__root.tsx": "import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'\nimport { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'\nimport { TanStackDevtools } from '@tanstack/react-devtools'\n\nimport Header from '../components/Header'\n\nimport appCss from '../styles.css?url'\n\nexport const Route = createRootRoute({\n head: () => ({\n meta: [\n {\n charSet: 'utf-8',\n },\n {\n name: 'viewport',\n content: 'width=device-width, initial-scale=1',\n },\n {\n title: 'TanStack Start Starter',\n },\n ],\n links: [\n {\n rel: 'stylesheet',\n href: appCss,\n },\n ],\n }),\n\n shellComponent: RootDocument,\n})\n\nfunction RootDocument({ children }: { children: React.ReactNode }) {\n return (\n <html lang=\"en\">\n <head>\n <HeadContent />\n </head>\n <body>\n <Header />\n {children}\n <TanStackDevtools\n config={{\n position: 'bottom-right',\n }}\n plugins={[\n {\n name: 'Tanstack Router',\n render: <TanStackRouterDevtoolsPanel />,\n },\n ]}\n />\n <Scripts />\n </body>\n </html>\n )\n}\n",
12
12
  "/src/routes/demo/api.names.ts": "import { createFileRoute } from '@tanstack/react-router'\nimport { json } from '@tanstack/react-start'\n\nexport const Route = createFileRoute('/demo/api/names')({\n server: {\n handlers: {\n GET: () => json(['Alice', 'Bob', 'Charlie']),\n },\n },\n})\n",
13
13
  "/src/routes/demo/start.api-request.tsx": "import { useEffect, useState } from 'react'\n\nimport { createFileRoute } from '@tanstack/react-router'\n\nfunction getNames() {\n return fetch('/demo/api/names').then((res) => res.json() as Promise<string[]>)\n}\n\nexport const Route = createFileRoute('/demo/start/api-request')({\n component: Home,\n})\n\nfunction Home() {\n const [names, setNames] = useState<Array<string>>([])\n\n useEffect(() => {\n getNames().then(setNames)\n }, [])\n\n return (\n <div\n className=\"flex items-center justify-center min-h-screen p-4 text-white\"\n style={{\n backgroundColor: '#000',\n backgroundImage:\n 'radial-gradient(ellipse 60% 60% at 0% 100%, #444 0%, #222 60%, #000 100%)',\n }}\n >\n <div className=\"w-full max-w-2xl p-8 rounded-xl backdrop-blur-md bg-black/50 shadow-xl border-8 border-black/10\">\n <h1 className=\"text-2xl mb-4\">Start API Request Demo - Names List</h1>\n <ul className=\"mb-4 space-y-2\">\n {names.map((name) => (\n <li\n key={name}\n className=\"bg-white/10 border border-white/20 rounded-lg p-3 backdrop-blur-sm shadow-md\"\n >\n <span className=\"text-lg text-white\">{name}</span>\n </li>\n ))}\n </ul>\n </div>\n </div>\n )\n}\n",
@@ -1,11 +0,0 @@
1
- import * as Sentry from "@sentry/tanstackstart-react";
2
- import {
3
- createMiddleware,
4
- registerGlobalMiddleware,
5
- } from "@tanstack/react-start";
6
-
7
- registerGlobalMiddleware({
8
- middleware: [
9
- createMiddleware().server(Sentry.sentryGlobalServerMiddlewareHandler()),
10
- ],
11
- });