@helloleo/plugins 0.2.0-beta.3 → 0.2.0-beta.5

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.
@@ -1,71 +1,91 @@
1
- import * as React from "react";
2
- import * as RechartsPrimitive from "recharts";
1
+ import * as React from "react"
2
+ import * as RechartsPrimitive from "recharts"
3
+ import type { TooltipValueType } from "recharts"
3
4
 
4
- import { cn } from "@/lib/utils";
5
+ import { cn } from "@/lib/utils"
5
6
 
6
7
  // Format: { THEME_NAME: CSS_SELECTOR }
7
- const THEMES = { light: "", dark: ".dark" } as const;
8
+ const THEMES = { light: "", dark: ".dark" } as const
8
9
 
9
- export type ChartConfig = {
10
- [k in string]: {
11
- label?: React.ReactNode;
12
- icon?: React.ComponentType;
10
+ const INITIAL_DIMENSION = { width: 320, height: 200 } as const
11
+ type TooltipNameType = number | string
12
+
13
+ export type ChartConfig = Record<
14
+ string,
15
+ {
16
+ label?: React.ReactNode
17
+ icon?: React.ComponentType
13
18
  } & (
14
19
  | { color?: string; theme?: never }
15
20
  | { color?: never; theme: Record<keyof typeof THEMES, string> }
16
- );
17
- };
21
+ )
22
+ >
18
23
 
19
24
  type ChartContextProps = {
20
- config: ChartConfig;
21
- };
25
+ config: ChartConfig
26
+ }
22
27
 
23
- const ChartContext = React.createContext<ChartContextProps | null>(null);
28
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
24
29
 
25
30
  function useChart() {
26
- const context = React.useContext(ChartContext);
31
+ const context = React.useContext(ChartContext)
27
32
 
28
33
  if (!context) {
29
- throw new Error("useChart must be used within a <ChartContainer />");
34
+ throw new Error("useChart must be used within a <ChartContainer />")
30
35
  }
31
36
 
32
- return context;
37
+ return context
33
38
  }
34
39
 
35
- const ChartContainer = React.forwardRef<
36
- HTMLDivElement,
37
- React.ComponentProps<"div"> & {
38
- config: ChartConfig;
39
- children: React.ComponentProps<typeof RechartsPrimitive.ResponsiveContainer>["children"];
40
+ function ChartContainer({
41
+ id,
42
+ className,
43
+ children,
44
+ config,
45
+ initialDimension = INITIAL_DIMENSION,
46
+ ...props
47
+ }: React.ComponentProps<"div"> & {
48
+ config: ChartConfig
49
+ children: React.ComponentProps<
50
+ typeof RechartsPrimitive.ResponsiveContainer
51
+ >["children"]
52
+ initialDimension?: {
53
+ width: number
54
+ height: number
40
55
  }
41
- >(({ id, className, children, config, ...props }, ref) => {
42
- const uniqueId = React.useId();
43
- const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
56
+ }) {
57
+ const uniqueId = React.useId()
58
+ const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`
44
59
 
45
60
  return (
46
61
  <ChartContext.Provider value={{ config }}>
47
62
  <div
63
+ data-slot="chart"
48
64
  data-chart={chartId}
49
- ref={ref}
50
65
  className={cn(
51
- "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
52
- className,
66
+ "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
67
+ className
53
68
  )}
54
69
  {...props}
55
70
  >
56
71
  <ChartStyle id={chartId} config={config} />
57
- <RechartsPrimitive.ResponsiveContainer>{children}</RechartsPrimitive.ResponsiveContainer>
72
+ <RechartsPrimitive.ResponsiveContainer
73
+ initialDimension={initialDimension}
74
+ >
75
+ {children}
76
+ </RechartsPrimitive.ResponsiveContainer>
58
77
  </div>
59
78
  </ChartContext.Provider>
60
- );
61
- });
62
- ChartContainer.displayName = "Chart";
79
+ )
80
+ }
63
81
 
64
82
  const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
65
- const colorConfig = Object.entries(config).filter(([, config]) => config.theme || config.color);
83
+ const colorConfig = Object.entries(config).filter(
84
+ ([, config]) => config.theme ?? config.color
85
+ )
66
86
 
67
87
  if (!colorConfig.length) {
68
- return null;
88
+ return null
69
89
  }
70
90
 
71
91
  return (
@@ -77,203 +97,214 @@ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
77
97
  ${prefix} [data-chart=${id}] {
78
98
  ${colorConfig
79
99
  .map(([key, itemConfig]) => {
80
- const color = itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || itemConfig.color;
81
- return color ? ` --color-${key}: ${color};` : null;
100
+ const color =
101
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??
102
+ itemConfig.color
103
+ return color ? ` --color-${key}: ${color};` : null
82
104
  })
83
105
  .join("\n")}
84
106
  }
85
- `,
107
+ `
86
108
  )
87
109
  .join("\n"),
88
110
  }}
89
111
  />
90
- );
91
- };
92
-
93
- const ChartTooltip = RechartsPrimitive.Tooltip;
94
-
95
- const ChartTooltipContent = React.forwardRef<
96
- HTMLDivElement,
97
- React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
98
- React.ComponentProps<"div"> & {
99
- hideLabel?: boolean;
100
- hideIndicator?: boolean;
101
- indicator?: "line" | "dot" | "dashed";
102
- nameKey?: string;
103
- labelKey?: string;
112
+ )
113
+ }
114
+
115
+ const ChartTooltip = RechartsPrimitive.Tooltip
116
+
117
+ function ChartTooltipContent({
118
+ active,
119
+ payload,
120
+ className,
121
+ indicator = "dot",
122
+ hideLabel = false,
123
+ hideIndicator = false,
124
+ label,
125
+ labelFormatter,
126
+ labelClassName,
127
+ formatter,
128
+ color,
129
+ nameKey,
130
+ labelKey,
131
+ }: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
132
+ React.ComponentProps<"div"> & {
133
+ hideLabel?: boolean
134
+ hideIndicator?: boolean
135
+ indicator?: "line" | "dot" | "dashed"
136
+ nameKey?: string
137
+ labelKey?: string
138
+ } & Omit<
139
+ RechartsPrimitive.DefaultTooltipContentProps<
140
+ TooltipValueType,
141
+ TooltipNameType
142
+ >,
143
+ "accessibilityLayer"
144
+ >) {
145
+ const { config } = useChart()
146
+
147
+ const tooltipLabel = React.useMemo(() => {
148
+ if (hideLabel || !payload?.length) {
149
+ return null
104
150
  }
105
- >(
106
- (
107
- {
108
- active,
109
- payload,
110
- className,
111
- indicator = "dot",
112
- hideLabel = false,
113
- hideIndicator = false,
114
- label,
115
- labelFormatter,
116
- labelClassName,
117
- formatter,
118
- color,
119
- nameKey,
120
- labelKey,
121
- },
122
- ref,
123
- ) => {
124
- const { config } = useChart();
125
-
126
- const tooltipLabel = React.useMemo(() => {
127
- if (hideLabel || !payload?.length) {
128
- return null;
129
- }
130
-
131
- const [item] = payload;
132
- const key = `${labelKey || item?.dataKey || item?.name || "value"}`;
133
- const itemConfig = getPayloadConfigFromPayload(config, item, key);
134
- const value =
135
- !labelKey && typeof label === "string"
136
- ? config[label as keyof typeof config]?.label || label
137
- : itemConfig?.label;
138
-
139
- if (labelFormatter) {
140
- return (
141
- <div className={cn("font-medium", labelClassName)}>{labelFormatter(value, payload)}</div>
142
- );
143
- }
144
-
145
- if (!value) {
146
- return null;
147
- }
148
-
149
- return <div className={cn("font-medium", labelClassName)}>{value}</div>;
150
- }, [label, labelFormatter, payload, hideLabel, labelClassName, config, labelKey]);
151
-
152
- if (!active || !payload?.length) {
153
- return null;
151
+
152
+ const [item] = payload
153
+ const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`
154
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
155
+ const value =
156
+ !labelKey && typeof label === "string"
157
+ ? (config[label]?.label ?? label)
158
+ : itemConfig?.label
159
+
160
+ if (labelFormatter) {
161
+ return (
162
+ <div className={cn("font-medium", labelClassName)}>
163
+ {labelFormatter(value, payload)}
164
+ </div>
165
+ )
154
166
  }
155
167
 
156
- const nestLabel = payload.length === 1 && indicator !== "dot";
168
+ if (!value) {
169
+ return null
170
+ }
157
171
 
158
- return (
159
- <div
160
- ref={ref}
161
- className={cn(
162
- "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
163
- className,
164
- )}
165
- >
166
- {!nestLabel ? tooltipLabel : null}
167
- <div className="grid gap-1.5">
168
- {payload
169
- .filter((item) => item.type !== "none")
170
- .map((item, index) => {
171
- const key = `${nameKey || item.name || item.dataKey || "value"}`;
172
- const itemConfig = getPayloadConfigFromPayload(config, item, key);
173
- const indicatorColor = color || item.payload.fill || item.color;
174
-
175
- return (
176
- <div
177
- key={item.dataKey}
178
- className={cn(
179
- "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
180
- indicator === "dot" && "items-center",
181
- )}
182
- >
183
- {formatter && item?.value !== undefined && item.name ? (
184
- formatter(item.value, item.name, item, index, item.payload)
185
- ) : (
186
- <>
187
- {itemConfig?.icon ? (
188
- <itemConfig.icon />
189
- ) : (
190
- !hideIndicator && (
191
- <div
192
- className={cn(
193
- "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
194
- {
195
- "h-2.5 w-2.5": indicator === "dot",
196
- "w-1": indicator === "line",
197
- "w-0 border-[1.5px] border-dashed bg-transparent":
198
- indicator === "dashed",
199
- "my-0.5": nestLabel && indicator === "dashed",
200
- },
201
- )}
202
- style={
203
- {
204
- "--color-bg": indicatorColor,
205
- "--color-border": indicatorColor,
206
- } as React.CSSProperties
172
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
173
+ }, [
174
+ label,
175
+ labelFormatter,
176
+ payload,
177
+ hideLabel,
178
+ labelClassName,
179
+ config,
180
+ labelKey,
181
+ ])
182
+
183
+ if (!active || !payload?.length) {
184
+ return null
185
+ }
186
+
187
+ const nestLabel = payload.length === 1 && indicator !== "dot"
188
+
189
+ return (
190
+ <div
191
+ className={cn(
192
+ "grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
193
+ className
194
+ )}
195
+ >
196
+ {!nestLabel ? tooltipLabel : null}
197
+ <div className="grid gap-1.5">
198
+ {payload
199
+ .filter((item) => item.type !== "none")
200
+ .map((item, index) => {
201
+ const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`
202
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
203
+ const indicatorColor = color ?? item.payload?.fill ?? item.color
204
+
205
+ return (
206
+ <div
207
+ key={index}
208
+ className={cn(
209
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
210
+ indicator === "dot" && "items-center"
211
+ )}
212
+ >
213
+ {formatter && item?.value !== undefined && item.name ? (
214
+ formatter(item.value, item.name, item, index, item.payload)
215
+ ) : (
216
+ <>
217
+ {itemConfig?.icon ? (
218
+ <itemConfig.icon />
219
+ ) : (
220
+ !hideIndicator && (
221
+ <div
222
+ className={cn(
223
+ "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
224
+ {
225
+ "h-2.5 w-2.5": indicator === "dot",
226
+ "w-1": indicator === "line",
227
+ "w-0 border-[1.5px] border-dashed bg-transparent":
228
+ indicator === "dashed",
229
+ "my-0.5": nestLabel && indicator === "dashed",
207
230
  }
208
- />
209
- )
231
+ )}
232
+ style={
233
+ {
234
+ "--color-bg": indicatorColor,
235
+ "--color-border": indicatorColor,
236
+ } as React.CSSProperties
237
+ }
238
+ />
239
+ )
240
+ )}
241
+ <div
242
+ className={cn(
243
+ "flex flex-1 justify-between leading-none",
244
+ nestLabel ? "items-end" : "items-center"
210
245
  )}
211
- <div
212
- className={cn(
213
- "flex flex-1 justify-between leading-none",
214
- nestLabel ? "items-end" : "items-center",
215
- )}
216
- >
217
- <div className="grid gap-1.5">
218
- {nestLabel ? tooltipLabel : null}
219
- <span className="text-muted-foreground">
220
- {itemConfig?.label || item.name}
221
- </span>
222
- </div>
223
- {item.value && (
224
- <span className="font-mono font-medium tabular-nums text-foreground">
225
- {item.value.toLocaleString()}
226
- </span>
227
- )}
246
+ >
247
+ <div className="grid gap-1.5">
248
+ {nestLabel ? tooltipLabel : null}
249
+ <span className="text-muted-foreground">
250
+ {itemConfig?.label ?? item.name}
251
+ </span>
228
252
  </div>
229
- </>
230
- )}
231
- </div>
232
- );
233
- })}
234
- </div>
253
+ {item.value != null && (
254
+ <span className="font-mono font-medium text-foreground tabular-nums">
255
+ {typeof item.value === "number"
256
+ ? item.value.toLocaleString()
257
+ : String(item.value)}
258
+ </span>
259
+ )}
260
+ </div>
261
+ </>
262
+ )}
263
+ </div>
264
+ )
265
+ })}
235
266
  </div>
236
- );
237
- },
238
- );
239
- ChartTooltipContent.displayName = "ChartTooltip";
240
-
241
- const ChartLegend = RechartsPrimitive.Legend;
242
-
243
- const ChartLegendContent = React.forwardRef<
244
- HTMLDivElement,
245
- React.ComponentProps<"div"> &
246
- Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
247
- hideIcon?: boolean;
248
- nameKey?: string;
249
- }
250
- >(({ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey }, ref) => {
251
- const { config } = useChart();
267
+ </div>
268
+ )
269
+ }
270
+
271
+ const ChartLegend = RechartsPrimitive.Legend
272
+
273
+ function ChartLegendContent({
274
+ className,
275
+ hideIcon = false,
276
+ payload,
277
+ verticalAlign = "bottom",
278
+ nameKey,
279
+ }: React.ComponentProps<"div"> & {
280
+ hideIcon?: boolean
281
+ nameKey?: string
282
+ } & RechartsPrimitive.DefaultLegendContentProps) {
283
+ const { config } = useChart()
252
284
 
253
285
  if (!payload?.length) {
254
- return null;
286
+ return null
255
287
  }
256
288
 
257
289
  return (
258
290
  <div
259
- ref={ref}
260
291
  className={cn(
261
292
  "flex items-center justify-center gap-4",
262
293
  verticalAlign === "top" ? "pb-3" : "pt-3",
263
- className,
294
+ className
264
295
  )}
265
296
  >
266
297
  {payload
267
298
  .filter((item) => item.type !== "none")
268
- .map((item) => {
269
- const key = `${nameKey || item.dataKey || "value"}`;
270
- const itemConfig = getPayloadConfigFromPayload(config, item, key);
299
+ .map((item, index) => {
300
+ const key = `${nameKey ?? item.dataKey ?? "value"}`
301
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
271
302
 
272
303
  return (
273
304
  <div
274
- key={item.value}
305
+ key={index}
275
306
  className={cn(
276
- "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground",
307
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
277
308
  )}
278
309
  >
279
310
  {itemConfig?.icon && !hideIcon ? (
@@ -288,37 +319,46 @@ const ChartLegendContent = React.forwardRef<
288
319
  )}
289
320
  {itemConfig?.label}
290
321
  </div>
291
- );
322
+ )
292
323
  })}
293
324
  </div>
294
- );
295
- });
296
- ChartLegendContent.displayName = "ChartLegend";
325
+ )
326
+ }
297
327
 
298
- // Helper to extract item config from a payload.
299
- function getPayloadConfigFromPayload(config: ChartConfig, payload: unknown, key: string) {
328
+ function getPayloadConfigFromPayload(
329
+ config: ChartConfig,
330
+ payload: unknown,
331
+ key: string
332
+ ) {
300
333
  if (typeof payload !== "object" || payload === null) {
301
- return undefined;
334
+ return undefined
302
335
  }
303
336
 
304
337
  const payloadPayload =
305
- "payload" in payload && typeof payload.payload === "object" && payload.payload !== null
338
+ "payload" in payload &&
339
+ typeof payload.payload === "object" &&
340
+ payload.payload !== null
306
341
  ? payload.payload
307
- : undefined;
342
+ : undefined
308
343
 
309
- let configLabelKey: string = key;
344
+ let configLabelKey: string = key
310
345
 
311
- if (key in payload && typeof payload[key as keyof typeof payload] === "string") {
312
- configLabelKey = payload[key as keyof typeof payload] as string;
346
+ if (
347
+ key in payload &&
348
+ typeof payload[key as keyof typeof payload] === "string"
349
+ ) {
350
+ configLabelKey = payload[key as keyof typeof payload] as string
313
351
  } else if (
314
352
  payloadPayload &&
315
353
  key in payloadPayload &&
316
354
  typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
317
355
  ) {
318
- configLabelKey = payloadPayload[key as keyof typeof payloadPayload] as string;
356
+ configLabelKey = payloadPayload[
357
+ key as keyof typeof payloadPayload
358
+ ] as string
319
359
  }
320
360
 
321
- return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config];
361
+ return configLabelKey in config ? config[configLabelKey] : config[key]
322
362
  }
323
363
 
324
364
  export {
@@ -328,4 +368,4 @@ export {
328
368
  ChartLegend,
329
369
  ChartLegendContent,
330
370
  ChartStyle,
331
- };
371
+ }
@@ -10,33 +10,43 @@
10
10
 
11
11
  import { Route as rootRouteImport } from './routes/__root'
12
12
  import { Route as IndexRouteImport } from './routes/index'
13
+ import { Route as ApiHealthRouteImport } from './routes/api/health'
13
14
 
14
15
  const IndexRoute = IndexRouteImport.update({
15
16
  id: '/',
16
17
  path: '/',
17
18
  getParentRoute: () => rootRouteImport,
18
19
  } as any)
20
+ const ApiHealthRoute = ApiHealthRouteImport.update({
21
+ id: '/api/health',
22
+ path: '/api/health',
23
+ getParentRoute: () => rootRouteImport,
24
+ } as any)
19
25
 
20
26
  export interface FileRoutesByFullPath {
21
27
  '/': typeof IndexRoute
28
+ '/api/health': typeof ApiHealthRoute
22
29
  }
23
30
  export interface FileRoutesByTo {
24
31
  '/': typeof IndexRoute
32
+ '/api/health': typeof ApiHealthRoute
25
33
  }
26
34
  export interface FileRoutesById {
27
35
  __root__: typeof rootRouteImport
28
36
  '/': typeof IndexRoute
37
+ '/api/health': typeof ApiHealthRoute
29
38
  }
30
39
  export interface FileRouteTypes {
31
40
  fileRoutesByFullPath: FileRoutesByFullPath
32
- fullPaths: '/'
41
+ fullPaths: '/' | '/api/health'
33
42
  fileRoutesByTo: FileRoutesByTo
34
- to: '/'
35
- id: '__root__' | '/'
43
+ to: '/' | '/api/health'
44
+ id: '__root__' | '/' | '/api/health'
36
45
  fileRoutesById: FileRoutesById
37
46
  }
38
47
  export interface RootRouteChildren {
39
48
  IndexRoute: typeof IndexRoute
49
+ ApiHealthRoute: typeof ApiHealthRoute
40
50
  }
41
51
 
42
52
  declare module '@tanstack/react-router' {
@@ -48,12 +58,29 @@ declare module '@tanstack/react-router' {
48
58
  preLoaderRoute: typeof IndexRouteImport
49
59
  parentRoute: typeof rootRouteImport
50
60
  }
61
+ '/api/health': {
62
+ id: '/api/health'
63
+ path: '/api/health'
64
+ fullPath: '/api/health'
65
+ preLoaderRoute: typeof ApiHealthRouteImport
66
+ parentRoute: typeof rootRouteImport
67
+ }
51
68
  }
52
69
  }
53
70
 
54
71
  const rootRouteChildren: RootRouteChildren = {
55
72
  IndexRoute: IndexRoute,
73
+ ApiHealthRoute: ApiHealthRoute,
56
74
  }
57
75
  export const routeTree = rootRouteImport
58
76
  ._addFileChildren(rootRouteChildren)
59
77
  ._addFileTypes<FileRouteTypes>()
78
+
79
+ import type { getRouter } from './router.tsx'
80
+ import type { createStart } from '@tanstack/react-start'
81
+ declare module '@tanstack/react-start' {
82
+ interface Register {
83
+ ssr: true
84
+ router: Awaited<ReturnType<typeof getRouter>>
85
+ }
86
+ }
@@ -23,6 +23,21 @@ TanStack Start uses file-based routing. Every `.ts` and `.tsx` file in this dire
23
23
  - Do not `export default` route components, it breaks code splitting. Use a named function passed as `component:`.
24
24
  - `routeTree.gen.ts` is auto-generated. Don't edit it by hand.
25
25
 
26
+ ## API routes vs server functions
27
+
28
+ TanStack Start offers two ways to run server-side code:
29
+
30
+ - [Server routes](https://tanstack.com/start/latest/docs/framework/react/guide/server-routes) — files under `src/routes/api/` that export HTTP handlers.
31
+ - [Server functions](https://tanstack.com/start/latest/docs/framework/react/guide/server-functions) — `createServerFn()` calls colocated with components.
32
+
33
+ Prefer API routes. Create them under `src/routes/api/` following the same
34
+ convention as `src/routes/api/health.ts` (one handler per HTTP method under
35
+ `server.handlers`, returning `Response.json()`). This is more scalable and
36
+ gives the user visually readable endpoints (`/api/todos`, `/api/todos/:id`)
37
+ they can recognize in the network monitor. Server functions are served from
38
+ generated URLs that read as random gibberish, which makes requests hard to
39
+ identify and debug.
40
+
26
41
  ## Authenticated routes
27
42
 
28
43
  Put protected pages inside a pathless `_authed` layout and keep login/signup