@benjavicente/start-client-core 1.167.9

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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/bin/intent.js +25 -0
  4. package/dist/esm/client/ServerFunctionSerializationAdapter.d.ts +7 -0
  5. package/dist/esm/client/ServerFunctionSerializationAdapter.js +18 -0
  6. package/dist/esm/client/ServerFunctionSerializationAdapter.js.map +1 -0
  7. package/dist/esm/client/hydrateStart.d.ts +2 -0
  8. package/dist/esm/client/hydrateStart.js +31 -0
  9. package/dist/esm/client/hydrateStart.js.map +1 -0
  10. package/dist/esm/client/index.d.ts +2 -0
  11. package/dist/esm/client/index.js +2 -0
  12. package/dist/esm/client-rpc/createClientRpc.d.ts +6 -0
  13. package/dist/esm/client-rpc/createClientRpc.js +21 -0
  14. package/dist/esm/client-rpc/createClientRpc.js.map +1 -0
  15. package/dist/esm/client-rpc/frame-decoder.d.ts +23 -0
  16. package/dist/esm/client-rpc/frame-decoder.js +231 -0
  17. package/dist/esm/client-rpc/frame-decoder.js.map +1 -0
  18. package/dist/esm/client-rpc/index.d.ts +1 -0
  19. package/dist/esm/client-rpc/index.js +2 -0
  20. package/dist/esm/client-rpc/serverFnFetcher.d.ts +1 -0
  21. package/dist/esm/client-rpc/serverFnFetcher.js +231 -0
  22. package/dist/esm/client-rpc/serverFnFetcher.js.map +1 -0
  23. package/dist/esm/constants.d.ts +53 -0
  24. package/dist/esm/constants.js +46 -0
  25. package/dist/esm/constants.js.map +1 -0
  26. package/dist/esm/createMiddleware.d.ts +195 -0
  27. package/dist/esm/createMiddleware.js +26 -0
  28. package/dist/esm/createMiddleware.js.map +1 -0
  29. package/dist/esm/createServerFn.d.ts +131 -0
  30. package/dist/esm/createServerFn.js +200 -0
  31. package/dist/esm/createServerFn.js.map +1 -0
  32. package/dist/esm/createStart.d.ts +50 -0
  33. package/dist/esm/createStart.js +29 -0
  34. package/dist/esm/createStart.js.map +1 -0
  35. package/dist/esm/fake-start-entry.d.ts +2 -0
  36. package/dist/esm/fake-start-entry.js +7 -0
  37. package/dist/esm/fake-start-entry.js.map +1 -0
  38. package/dist/esm/getDefaultSerovalPlugins.d.ts +1 -0
  39. package/dist/esm/getDefaultSerovalPlugins.js +10 -0
  40. package/dist/esm/getDefaultSerovalPlugins.js.map +1 -0
  41. package/dist/esm/getGlobalStartContext.d.ts +3 -0
  42. package/dist/esm/getGlobalStartContext.js +12 -0
  43. package/dist/esm/getGlobalStartContext.js.map +1 -0
  44. package/dist/esm/getRouterInstance.d.ts +2 -0
  45. package/dist/esm/getRouterInstance.js +8 -0
  46. package/dist/esm/getRouterInstance.js.map +1 -0
  47. package/dist/esm/getStartContextServerOnly.d.ts +2 -0
  48. package/dist/esm/getStartContextServerOnly.js +8 -0
  49. package/dist/esm/getStartContextServerOnly.js.map +1 -0
  50. package/dist/esm/getStartOptions.d.ts +2 -0
  51. package/dist/esm/getStartOptions.js +8 -0
  52. package/dist/esm/getStartOptions.js.map +1 -0
  53. package/dist/esm/global.d.ts +7 -0
  54. package/dist/esm/index.d.ts +20 -0
  55. package/dist/esm/index.js +12 -0
  56. package/dist/esm/safeObjectMerge.d.ts +10 -0
  57. package/dist/esm/safeObjectMerge.js +30 -0
  58. package/dist/esm/safeObjectMerge.js.map +1 -0
  59. package/dist/esm/serverRoute.d.ts +65 -0
  60. package/dist/esm/startEntry.d.ts +8 -0
  61. package/dist/esm/tests/createServerFn.test-d.d.ts +1 -0
  62. package/dist/esm/tests/createServerMiddleware.test-d.d.ts +1 -0
  63. package/package.json +98 -0
  64. package/skills/start-core/SKILL.md +210 -0
  65. package/skills/start-core/deployment/SKILL.md +306 -0
  66. package/skills/start-core/execution-model/SKILL.md +302 -0
  67. package/skills/start-core/middleware/SKILL.md +365 -0
  68. package/skills/start-core/server-functions/SKILL.md +335 -0
  69. package/skills/start-core/server-routes/SKILL.md +280 -0
  70. package/src/client/ServerFunctionSerializationAdapter.ts +16 -0
  71. package/src/client/hydrateStart.ts +43 -0
  72. package/src/client/index.ts +2 -0
  73. package/src/client-rpc/createClientRpc.ts +20 -0
  74. package/src/client-rpc/frame-decoder.ts +389 -0
  75. package/src/client-rpc/index.ts +1 -0
  76. package/src/client-rpc/serverFnFetcher.ts +416 -0
  77. package/src/constants.ts +90 -0
  78. package/src/createMiddleware.ts +824 -0
  79. package/src/createServerFn.ts +813 -0
  80. package/src/createStart.ts +166 -0
  81. package/src/fake-start-entry.ts +2 -0
  82. package/src/getDefaultSerovalPlugins.ts +17 -0
  83. package/src/getGlobalStartContext.ts +18 -0
  84. package/src/getRouterInstance.ts +8 -0
  85. package/src/getStartContextServerOnly.ts +4 -0
  86. package/src/getStartOptions.ts +8 -0
  87. package/src/global.ts +9 -0
  88. package/src/index.tsx +119 -0
  89. package/src/safeObjectMerge.ts +38 -0
  90. package/src/serverRoute.ts +509 -0
  91. package/src/start-entry.d.ts +11 -0
  92. package/src/startEntry.ts +10 -0
  93. package/src/tests/createServerFn.test-d.ts +866 -0
  94. package/src/tests/createServerMiddleware.test-d.ts +810 -0
@@ -0,0 +1,365 @@
1
+ ---
2
+ name: start-core/middleware
3
+ description: >-
4
+ createMiddleware, request middleware (.server only), server function
5
+ middleware (.client + .server), context passing via next({ context }),
6
+ sendContext for client-server transfer, global middleware via
7
+ createStart in src/start.ts, middleware factories, method order
8
+ enforcement, fetch override precedence.
9
+ type: sub-skill
10
+ library: tanstack-start
11
+ library_version: '1.166.2'
12
+ requires:
13
+ - start-core
14
+ - start-core/server-functions
15
+ sources:
16
+ - TanStack/router:docs/start/framework/react/guide/middleware.md
17
+ ---
18
+
19
+ # Middleware
20
+
21
+ Middleware customizes the behavior of server functions and server routes. It is composable — middleware can depend on other middleware to form a chain.
22
+
23
+ > **CRITICAL**: TypeScript enforces method order: `middleware()` → `inputValidator()` → `client()` → `server()`. Wrong order causes type errors.
24
+ > **CRITICAL**: Client context sent via `sendContext` is NOT validated by default. If you send dynamic user-generated data, validate it in server-side middleware before use.
25
+
26
+ ## Two Types of Middleware
27
+
28
+ | Feature | Request Middleware | Server Function Middleware |
29
+ | ----------------- | -------------------------------------------- | ---------------------------------------- |
30
+ | Scope | All server requests (SSR, routes, functions) | Server functions only |
31
+ | Methods | `.server()` | `.client()`, `.server()` |
32
+ | Input validation | No | Yes (`.inputValidator()`) |
33
+ | Client-side logic | No | Yes |
34
+ | Created with | `createMiddleware()` | `createMiddleware({ type: 'function' })` |
35
+
36
+ Request middleware cannot depend on server function middleware. Server function middleware can depend on both types.
37
+
38
+ ## Request Middleware
39
+
40
+ Runs on ALL server requests (SSR, server routes, server functions):
41
+
42
+ ```tsx
43
+ // Use @tanstack/<framework>-start for your framework (react, solid, vue)
44
+ import { createMiddleware } from '@benjavicente/react-start'
45
+
46
+ const loggingMiddleware = createMiddleware().server(
47
+ async ({ next, context, request }) => {
48
+ console.log('Request:', request.url)
49
+ const result = await next()
50
+ return result
51
+ },
52
+ )
53
+ ```
54
+
55
+ ## Server Function Middleware
56
+
57
+ Has both client and server phases:
58
+
59
+ ```tsx
60
+ // Use @tanstack/<framework>-start for your framework (react, solid, vue)
61
+ import { createMiddleware } from '@benjavicente/react-start'
62
+
63
+ const authMiddleware = createMiddleware({ type: 'function' })
64
+ .client(async ({ next }) => {
65
+ // Runs on client BEFORE the RPC call
66
+ const result = await next()
67
+ // Runs on client AFTER the RPC response
68
+ return result
69
+ })
70
+ .server(async ({ next, context }) => {
71
+ // Runs on server BEFORE the handler
72
+ const result = await next()
73
+ // Runs on server AFTER the handler
74
+ return result
75
+ })
76
+ ```
77
+
78
+ ## Attaching Middleware to Server Functions
79
+
80
+ ```tsx
81
+ // Use @tanstack/<framework>-start for your framework (react, solid, vue)
82
+ import { createServerFn } from '@benjavicente/react-start'
83
+
84
+ const fn = createServerFn()
85
+ .middleware([authMiddleware])
86
+ .handler(async ({ context }) => {
87
+ // context contains data from middleware
88
+ return { user: context.user }
89
+ })
90
+ ```
91
+
92
+ ## Context Passing via next()
93
+
94
+ Pass context down the middleware chain:
95
+
96
+ ```tsx
97
+ const authMiddleware = createMiddleware().server(async ({ next, request }) => {
98
+ const session = await getSession(request.headers)
99
+ if (!session) throw new Error('Unauthorized')
100
+
101
+ return next({
102
+ context: { session },
103
+ })
104
+ })
105
+
106
+ const roleMiddleware = createMiddleware()
107
+ .middleware([authMiddleware])
108
+ .server(async ({ next, context }) => {
109
+ console.log('Session:', context.session) // typed!
110
+ return next()
111
+ })
112
+ ```
113
+
114
+ ## Sending Context Between Client and Server
115
+
116
+ ### Client → Server (sendContext)
117
+
118
+ ```tsx
119
+ const workspaceMiddleware = createMiddleware({ type: 'function' })
120
+ .client(async ({ next, context }) => {
121
+ return next({
122
+ sendContext: {
123
+ workspaceId: context.workspaceId,
124
+ },
125
+ })
126
+ })
127
+ .server(async ({ next, context }) => {
128
+ // workspaceId available here, but VALIDATE IT
129
+ console.log('Workspace:', context.workspaceId)
130
+ return next()
131
+ })
132
+ ```
133
+
134
+ ### Server → Client (sendContext in server)
135
+
136
+ ```tsx
137
+ const serverTimer = createMiddleware({ type: 'function' }).server(
138
+ async ({ next }) => {
139
+ return next({
140
+ sendContext: {
141
+ timeFromServer: new Date(),
142
+ },
143
+ })
144
+ },
145
+ )
146
+
147
+ const clientLogger = createMiddleware({ type: 'function' })
148
+ .middleware([serverTimer])
149
+ .client(async ({ next }) => {
150
+ const result = await next()
151
+ console.log('Server time:', result.context.timeFromServer)
152
+ return result
153
+ })
154
+ ```
155
+
156
+ ## Input Validation in Middleware
157
+
158
+ ```tsx
159
+ import { z } from 'zod'
160
+ import { zodValidator } from '@benjavicente/zod-adapter'
161
+
162
+ const workspaceMiddleware = createMiddleware({ type: 'function' })
163
+ .inputValidator(zodValidator(z.object({ workspaceId: z.string() })))
164
+ .server(async ({ next, data }) => {
165
+ console.log('Workspace:', data.workspaceId)
166
+ return next()
167
+ })
168
+ ```
169
+
170
+ ## Global Middleware
171
+
172
+ Create `src/start.ts` to configure global middleware:
173
+
174
+ ```tsx
175
+ // src/start.ts
176
+ // Use @tanstack/<framework>-start for your framework (react, solid, vue)
177
+ import { createStart, createMiddleware } from '@benjavicente/react-start'
178
+
179
+ const requestLogger = createMiddleware().server(async ({ next, request }) => {
180
+ console.log(`${request.method} ${request.url}`)
181
+ return next()
182
+ })
183
+
184
+ const functionAuth = createMiddleware({ type: 'function' }).server(
185
+ async ({ next }) => {
186
+ // runs for every server function
187
+ return next()
188
+ },
189
+ )
190
+
191
+ export const startInstance = createStart(() => ({
192
+ requestMiddleware: [requestLogger],
193
+ functionMiddleware: [functionAuth],
194
+ }))
195
+ ```
196
+
197
+ ## Using Middleware with Server Routes
198
+
199
+ ### All handlers in a route
200
+
201
+ ```tsx
202
+ export const Route = createFileRoute('/api/users')({
203
+ server: {
204
+ middleware: [authMiddleware],
205
+ handlers: {
206
+ GET: async ({ context }) => Response.json(context.user),
207
+ POST: async ({ request }) => {
208
+ /* ... */
209
+ },
210
+ },
211
+ },
212
+ })
213
+ ```
214
+
215
+ ### Specific handlers only
216
+
217
+ ```tsx
218
+ export const Route = createFileRoute('/api/users')({
219
+ server: {
220
+ handlers: ({ createHandlers }) =>
221
+ createHandlers({
222
+ GET: async () => Response.json({ public: true }),
223
+ POST: {
224
+ middleware: [authMiddleware],
225
+ handler: async ({ context }) => {
226
+ return Response.json({ user: context.session.user })
227
+ },
228
+ },
229
+ }),
230
+ },
231
+ })
232
+ ```
233
+
234
+ ## Middleware Factories
235
+
236
+ Create parameterized middleware for reusable patterns like authorization:
237
+
238
+ ```tsx
239
+ const authMiddleware = createMiddleware().server(async ({ next, request }) => {
240
+ const session = await auth.getSession({ headers: request.headers })
241
+ if (!session) throw new Error('Unauthorized')
242
+ return next({ context: { session } })
243
+ })
244
+
245
+ type Permissions = Record<string, string[]>
246
+
247
+ function authorizationMiddleware(permissions: Permissions) {
248
+ return createMiddleware({ type: 'function' })
249
+ .middleware([authMiddleware])
250
+ .server(async ({ next, context }) => {
251
+ const granted = await auth.hasPermission(context.session, permissions)
252
+ if (!granted) throw new Error('Forbidden')
253
+ return next()
254
+ })
255
+ }
256
+
257
+ // Usage
258
+ const getClients = createServerFn()
259
+ .middleware([authorizationMiddleware({ client: ['read'] })])
260
+ .handler(async () => {
261
+ return { message: 'The user can read clients.' }
262
+ })
263
+ ```
264
+
265
+ ## Custom Headers and Fetch
266
+
267
+ ### Setting headers from client middleware
268
+
269
+ ```tsx
270
+ const authMiddleware = createMiddleware({ type: 'function' }).client(
271
+ async ({ next }) => {
272
+ return next({
273
+ headers: { Authorization: `Bearer ${getToken()}` },
274
+ })
275
+ },
276
+ )
277
+ ```
278
+
279
+ Headers merge across middleware. Later middleware overrides earlier. Call-site headers override all middleware headers.
280
+
281
+ ### Custom fetch
282
+
283
+ ```tsx
284
+ // Use @tanstack/<framework>-start for your framework (react, solid, vue)
285
+ import type { CustomFetch } from '@benjavicente/react-start'
286
+
287
+ const loggingMiddleware = createMiddleware({ type: 'function' }).client(
288
+ async ({ next }) => {
289
+ const customFetch: CustomFetch = async (url, init) => {
290
+ console.log('Request:', url)
291
+ return fetch(url, init)
292
+ }
293
+ return next({ fetch: customFetch })
294
+ },
295
+ )
296
+ ```
297
+
298
+ Fetch precedence (highest to lowest): call site → later middleware → earlier middleware → createStart global → default fetch.
299
+
300
+ ## Common Mistakes
301
+
302
+ ### 1. HIGH: Trusting client sendContext without validation
303
+
304
+ ```tsx
305
+ // WRONG — client can send arbitrary data
306
+ .server(async ({ next, context }) => {
307
+ await db.query(`SELECT * FROM workspace_${context.workspaceId}`)
308
+ return next()
309
+ })
310
+
311
+ // CORRECT — validate before use
312
+ .server(async ({ next, context }) => {
313
+ const workspaceId = z.string().uuid().parse(context.workspaceId)
314
+ await db.query('SELECT * FROM workspaces WHERE id = $1', [workspaceId])
315
+ return next()
316
+ })
317
+ ```
318
+
319
+ ### 2. MEDIUM: Confusing request vs server function middleware
320
+
321
+ Request middleware runs on ALL requests (SSR, routes, functions). Server function middleware runs only for `createServerFn` calls and has `.client()` method.
322
+
323
+ ### 3. HIGH: Browser APIs in .client() crash during SSR
324
+
325
+ During SSR, `.client()` callbacks run on the server. Browser-only APIs like `localStorage` or `window` will throw `ReferenceError`:
326
+
327
+ ```tsx
328
+ // WRONG — localStorage doesn't exist on the server during SSR
329
+ const middleware = createMiddleware({ type: 'function' }).client(
330
+ async ({ next }) => {
331
+ const token = localStorage.getItem('token')
332
+ return next({ sendContext: { token } })
333
+ },
334
+ )
335
+
336
+ // CORRECT — use cookies/headers or guard with typeof window check
337
+ const middleware = createMiddleware({ type: 'function' }).client(
338
+ async ({ next }) => {
339
+ const token =
340
+ typeof window !== 'undefined' ? localStorage.getItem('token') : null
341
+ return next({ sendContext: { token } })
342
+ },
343
+ )
344
+ ```
345
+
346
+ ### 4. MEDIUM: Wrong method order
347
+
348
+ ```tsx
349
+ // WRONG — type error
350
+ createMiddleware({ type: 'function' })
351
+ .server(() => { ... })
352
+ .client(() => { ... })
353
+
354
+ // CORRECT — middleware → inputValidator → client → server
355
+ createMiddleware({ type: 'function' })
356
+ .middleware([dep])
357
+ .inputValidator(schema)
358
+ .client(({ next }) => next())
359
+ .server(({ next }) => next())
360
+ ```
361
+
362
+ ## Cross-References
363
+
364
+ - [start-core/server-functions](../server-functions/SKILL.md) — what middleware wraps
365
+ - [start-core/server-routes](../server-routes/SKILL.md) — middleware on API endpoints
@@ -0,0 +1,335 @@
1
+ ---
2
+ name: start-core/server-functions
3
+ description: >-
4
+ createServerFn (GET/POST), inputValidator (Zod or function),
5
+ useServerFn hook, server context utilities (getRequest,
6
+ getRequestHeader, setResponseHeader, setResponseStatus), error
7
+ handling (throw errors, redirect, notFound), streaming, FormData
8
+ handling, file organization (.functions.ts, .server.ts).
9
+ type: sub-skill
10
+ library: tanstack-start
11
+ library_version: '1.166.2'
12
+ requires:
13
+ - start-core
14
+ sources:
15
+ - TanStack/router:docs/start/framework/react/guide/server-functions.md
16
+ ---
17
+
18
+ # Server Functions
19
+
20
+ Server functions are type-safe RPCs created with `createServerFn`. They run exclusively on the server but can be called from anywhere — loaders, components, hooks, event handlers, or other server functions.
21
+
22
+ > **CRITICAL**: Loaders are ISOMORPHIC — they run on BOTH client and server. Database queries, file system access, and secret API keys MUST go inside `createServerFn`, NOT in loaders directly.
23
+ > **CRITICAL**: Do not use `"use server"` directives, `getServerSideProps`, or any Next.js/Remix server patterns. TanStack Start uses `createServerFn` exclusively.
24
+
25
+ ## Basic Usage
26
+
27
+ ```tsx
28
+ import { createServerFn } from '@benjavicente/react-start'
29
+
30
+ // GET (default)
31
+ const getData = createServerFn().handler(async () => {
32
+ return { message: 'Hello from server!' }
33
+ })
34
+
35
+ // POST
36
+ const saveData = createServerFn({ method: 'POST' }).handler(async () => {
37
+ return { success: true }
38
+ })
39
+ ```
40
+
41
+ ## Calling from Loaders
42
+
43
+ ```tsx
44
+ import { createFileRoute } from '@benjavicente/react-router'
45
+ import { createServerFn } from '@benjavicente/react-start'
46
+
47
+ const getPosts = createServerFn({ method: 'GET' }).handler(async () => {
48
+ const posts = await db.query('SELECT * FROM posts')
49
+ return { posts }
50
+ })
51
+
52
+ export const Route = createFileRoute('/posts')({
53
+ loader: () => getPosts(),
54
+ component: PostList,
55
+ })
56
+
57
+ function PostList() {
58
+ const { posts } = Route.useLoaderData()
59
+ return (
60
+ <ul>
61
+ {posts.map((p) => (
62
+ <li key={p.id}>{p.title}</li>
63
+ ))}
64
+ </ul>
65
+ )
66
+ }
67
+ ```
68
+
69
+ ## Calling from Components
70
+
71
+ Use the `useServerFn` hook to call server functions from event handlers:
72
+
73
+ ```tsx
74
+ import { useServerFn } from '@benjavicente/react-start'
75
+
76
+ const deletePost = createServerFn({ method: 'POST' })
77
+ .inputValidator((data: { id: string }) => data)
78
+ .handler(async ({ data }) => {
79
+ await db.delete('posts').where({ id: data.id })
80
+ return { success: true }
81
+ })
82
+
83
+ function DeleteButton({ postId }: { postId: string }) {
84
+ const deletePostFn = useServerFn(deletePost)
85
+
86
+ return (
87
+ <button onClick={() => deletePostFn({ data: { id: postId } })}>
88
+ Delete
89
+ </button>
90
+ )
91
+ }
92
+ ```
93
+
94
+ ## Input Validation
95
+
96
+ ### Basic Validator
97
+
98
+ ```tsx
99
+ const greetUser = createServerFn({ method: 'GET' })
100
+ .inputValidator((data: { name: string }) => data)
101
+ .handler(async ({ data }) => {
102
+ return `Hello, ${data.name}!`
103
+ })
104
+
105
+ await greetUser({ data: { name: 'John' } })
106
+ ```
107
+
108
+ ### Zod Validator
109
+
110
+ ```tsx
111
+ import { z } from 'zod'
112
+
113
+ const createUser = createServerFn({ method: 'POST' })
114
+ .inputValidator(
115
+ z.object({
116
+ name: z.string().min(1),
117
+ age: z.number().min(0),
118
+ }),
119
+ )
120
+ .handler(async ({ data }) => {
121
+ return `Created user: ${data.name}, age ${data.age}`
122
+ })
123
+ ```
124
+
125
+ ### FormData
126
+
127
+ ```tsx
128
+ const submitForm = createServerFn({ method: 'POST' })
129
+ .inputValidator((data) => {
130
+ if (!(data instanceof FormData)) {
131
+ throw new Error('Expected FormData')
132
+ }
133
+ return {
134
+ name: data.get('name')?.toString() || '',
135
+ email: data.get('email')?.toString() || '',
136
+ }
137
+ })
138
+ .handler(async ({ data }) => {
139
+ return { success: true }
140
+ })
141
+ ```
142
+
143
+ ## Error Handling
144
+
145
+ ### Errors
146
+
147
+ ```tsx
148
+ const riskyFunction = createServerFn().handler(async () => {
149
+ throw new Error('Something went wrong!')
150
+ })
151
+
152
+ try {
153
+ await riskyFunction()
154
+ } catch (error) {
155
+ console.log(error.message) // "Something went wrong!"
156
+ }
157
+ ```
158
+
159
+ ### Redirects
160
+
161
+ ```tsx
162
+ import { redirect } from '@benjavicente/react-router'
163
+
164
+ const requireAuth = createServerFn().handler(async () => {
165
+ const user = await getCurrentUser()
166
+ if (!user) {
167
+ throw redirect({ to: '/login' })
168
+ }
169
+ return user
170
+ })
171
+ ```
172
+
173
+ ### Not Found
174
+
175
+ ```tsx
176
+ import { notFound } from '@benjavicente/react-router'
177
+
178
+ const getPost = createServerFn()
179
+ .inputValidator((data: { id: string }) => data)
180
+ .handler(async ({ data }) => {
181
+ const post = await db.findPost(data.id)
182
+ if (!post) {
183
+ throw notFound()
184
+ }
185
+ return post
186
+ })
187
+ ```
188
+
189
+ ## Server Context Utilities
190
+
191
+ Access request/response details inside server function handlers:
192
+
193
+ ```tsx
194
+ import { createServerFn } from '@benjavicente/react-start'
195
+ import {
196
+ getRequest,
197
+ getRequestHeader,
198
+ setResponseHeaders,
199
+ setResponseStatus,
200
+ } from '@benjavicente/react-start/server'
201
+
202
+ const getCachedData = createServerFn({ method: 'GET' }).handler(async () => {
203
+ const request = getRequest()
204
+ const authHeader = getRequestHeader('Authorization')
205
+
206
+ setResponseHeaders({
207
+ 'Cache-Control': 'public, max-age=300',
208
+ })
209
+ setResponseStatus(200)
210
+
211
+ return fetchData()
212
+ })
213
+ ```
214
+
215
+ Available utilities:
216
+
217
+ - `getRequest()` — full Request object
218
+ - `getRequestHeader(name)` — single request header
219
+ - `setResponseHeader(name, value)` — single response header
220
+ - `setResponseHeaders(headers)` — multiple response headers
221
+ - `setResponseStatus(code)` — HTTP status code
222
+
223
+ ## File Organization
224
+
225
+ ```text
226
+ src/utils/
227
+ ├── users.functions.ts # createServerFn wrappers (safe to import anywhere)
228
+ ├── users.server.ts # Server-only helpers (DB queries, internal logic)
229
+ └── schemas.ts # Shared validation schemas (client-safe)
230
+ ```
231
+
232
+ ```tsx
233
+ // users.server.ts — server-only helpers
234
+ import { db } from '~/db'
235
+
236
+ export async function findUserById(id: string) {
237
+ return db.query.users.findFirst({ where: eq(users.id, id) })
238
+ }
239
+ ```
240
+
241
+ ```tsx
242
+ // users.functions.ts — server functions
243
+ import { createServerFn } from '@benjavicente/react-start'
244
+ import { findUserById } from './users.server'
245
+
246
+ export const getUser = createServerFn({ method: 'GET' })
247
+ .inputValidator((data: { id: string }) => data)
248
+ .handler(async ({ data }) => {
249
+ return findUserById(data.id)
250
+ })
251
+ ```
252
+
253
+ Static imports of server functions are safe — the build replaces implementations with RPC stubs in client bundles.
254
+
255
+ ## Common Mistakes
256
+
257
+ ### 1. CRITICAL: Putting server-only code in loaders
258
+
259
+ ```tsx
260
+ // WRONG — loader is ISOMORPHIC, runs on BOTH client and server
261
+ export const Route = createFileRoute('/posts')({
262
+ loader: async () => {
263
+ const posts = await db.query('SELECT * FROM posts')
264
+ return { posts }
265
+ },
266
+ })
267
+
268
+ // CORRECT — use createServerFn for server-only logic
269
+ const getPosts = createServerFn({ method: 'GET' }).handler(async () => {
270
+ const posts = await db.query('SELECT * FROM posts')
271
+ return { posts }
272
+ })
273
+
274
+ export const Route = createFileRoute('/posts')({
275
+ loader: () => getPosts(),
276
+ })
277
+ ```
278
+
279
+ ### 2. CRITICAL: Using Next.js/Remix server patterns
280
+
281
+ ```tsx
282
+ // WRONG — "use server" is a React directive, not used in TanStack Start
283
+ 'use server'
284
+ export async function getUser() { ... }
285
+
286
+ // WRONG — getServerSideProps is Next.js
287
+ export async function getServerSideProps() { ... }
288
+
289
+ // CORRECT — TanStack Start uses createServerFn
290
+ const getUser = createServerFn({ method: 'GET' })
291
+ .handler(async () => { ... })
292
+ ```
293
+
294
+ ### 3. HIGH: Dynamic imports for server functions
295
+
296
+ ```tsx
297
+ // WRONG — can cause bundler issues
298
+ const { getUser } = await import('~/utils/users.functions')
299
+
300
+ // CORRECT — static imports are safe, build handles environment shaking
301
+ import { getUser } from '~/utils/users.functions'
302
+ ```
303
+
304
+ ### 4. HIGH: Awaiting server function without calling it
305
+
306
+ `createServerFn` returns a function — it must be invoked with `()`:
307
+
308
+ ```tsx
309
+ // WRONG — getItems is a function, not a Promise
310
+ const data = await getItems
311
+
312
+ // CORRECT — call the function
313
+ const data = await getItems()
314
+
315
+ // With validated input
316
+ const data = await getItems({ data: { id: '1' } })
317
+ ```
318
+
319
+ ### 5. MEDIUM: Not using useServerFn for component calls
320
+
321
+ When calling server functions from event handlers in components, use `useServerFn` to get proper React integration:
322
+
323
+ ```tsx
324
+ // WRONG — direct call doesn't integrate with React lifecycle
325
+ <button onClick={() => deletePost({ data: { id } })}>Delete</button>
326
+
327
+ // CORRECT — useServerFn integrates with React
328
+ const deletePostFn = useServerFn(deletePost)
329
+ <button onClick={() => deletePostFn({ data: { id } })}>Delete</button>
330
+ ```
331
+
332
+ ## Cross-References
333
+
334
+ - [start-core/execution-model](../execution-model/SKILL.md) — understanding where code runs
335
+ - [start-core/middleware](../middleware/SKILL.md) — composing server functions with middleware