@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,306 @@
1
+ ---
2
+ name: start-core/deployment
3
+ description: >-
4
+ Deploy to Cloudflare Workers, Netlify, Vercel, Node.js/Docker,
5
+ Bun, Railway. Selective SSR (ssr option per route), SPA mode,
6
+ static prerendering, ISR with Cache-Control headers, SEO and
7
+ head management.
8
+ type: sub-skill
9
+ library: tanstack-start
10
+ library_version: '1.166.2'
11
+ requires:
12
+ - start-core
13
+ sources:
14
+ - TanStack/router:docs/start/framework/react/guide/hosting.md
15
+ - TanStack/router:docs/start/framework/react/guide/selective-ssr.md
16
+ - TanStack/router:docs/start/framework/react/guide/static-prerendering.md
17
+ - TanStack/router:docs/start/framework/react/guide/seo.md
18
+ ---
19
+
20
+ # Deployment and Rendering
21
+
22
+ TanStack Start deploys to any hosting provider via Vite and Nitro. This skill covers hosting setup, SSR configuration, prerendering, and SEO.
23
+
24
+ ## Hosting Providers
25
+
26
+ ### Cloudflare Workers
27
+
28
+ ```bash
29
+ pnpm add -D @cloudflare/vite-plugin wrangler
30
+ ```
31
+
32
+ ```ts
33
+ // vite.config.ts
34
+ import { defineConfig } from 'vite'
35
+ import { tanstackStart } from '@benjavicente/react-start/plugin/vite'
36
+ import { cloudflare } from '@cloudflare/vite-plugin'
37
+ import viteReact from '@vitejs/plugin-react'
38
+
39
+ export default defineConfig({
40
+ plugins: [
41
+ cloudflare({ viteEnvironment: { name: 'ssr' } }),
42
+ tanstackStart(),
43
+ viteReact(),
44
+ ],
45
+ })
46
+ ```
47
+
48
+ ```jsonc
49
+ // wrangler.jsonc
50
+ {
51
+ "name": "my-app",
52
+ "compatibility_date": "2025-09-02",
53
+ "compatibility_flags": ["nodejs_compat"],
54
+ "main": "@benjavicente/react-start/server-entry",
55
+ }
56
+ ```
57
+
58
+ Deploy: `npx wrangler login && pnpm run deploy`
59
+
60
+ ### Netlify
61
+
62
+ ```bash
63
+ pnpm add -D @netlify/vite-plugin-tanstack-start
64
+ ```
65
+
66
+ ```ts
67
+ // vite.config.ts
68
+ import { defineConfig } from 'vite'
69
+ import { tanstackStart } from '@benjavicente/react-start/plugin/vite'
70
+ import netlify from '@netlify/vite-plugin-tanstack-start'
71
+ import viteReact from '@vitejs/plugin-react'
72
+
73
+ export default defineConfig({
74
+ plugins: [tanstackStart(), netlify(), viteReact()],
75
+ })
76
+ ```
77
+
78
+ Deploy: `npx netlify deploy`
79
+
80
+ ### Nitro (Vercel, Railway, Node.js, Docker)
81
+
82
+ ```bash
83
+ npm install nitro@npm:nitro-nightly@latest
84
+ ```
85
+
86
+ ```ts
87
+ // vite.config.ts
88
+ import { defineConfig } from 'vite'
89
+ import { tanstackStart } from '@benjavicente/react-start/plugin/vite'
90
+ import { nitro } from 'nitro/vite'
91
+ import viteReact from '@vitejs/plugin-react'
92
+
93
+ export default defineConfig({
94
+ plugins: [tanstackStart(), nitro(), viteReact()],
95
+ })
96
+ ```
97
+
98
+ Build and start: `npm run build && node .output/server/index.mjs`
99
+
100
+ ### Bun
101
+
102
+ Bun deployment requires React 19. For React 18, use Node.js deployment.
103
+
104
+ ```ts
105
+ // vite.config.ts — add bun preset to nitro
106
+ plugins: [tanstackStart(), nitro({ preset: 'bun' }), viteReact()]
107
+ ```
108
+
109
+ ## Selective SSR
110
+
111
+ Control SSR per route with the `ssr` property.
112
+
113
+ ### `ssr: true` (default)
114
+
115
+ Runs `beforeLoad` and `loader` on server, renders component on server:
116
+
117
+ ```tsx
118
+ export const Route = createFileRoute('/posts/$postId')({
119
+ ssr: true, // default
120
+ loader: () => fetchPost(), // runs on server during SSR
121
+ component: PostPage, // rendered on server
122
+ })
123
+ ```
124
+
125
+ ### `ssr: false`
126
+
127
+ Disables server execution of `beforeLoad`/`loader` and server rendering:
128
+
129
+ ```tsx
130
+ export const Route = createFileRoute('/dashboard')({
131
+ ssr: false,
132
+ loader: () => fetchDashboard(), // runs on client only
133
+ component: DashboardPage, // rendered on client only
134
+ })
135
+ ```
136
+
137
+ ### `ssr: 'data-only'`
138
+
139
+ Runs `beforeLoad`/`loader` on server but renders component on client only:
140
+
141
+ ```tsx
142
+ export const Route = createFileRoute('/canvas')({
143
+ ssr: 'data-only',
144
+ loader: () => fetchCanvasData(), // runs on server
145
+ component: CanvasPage, // rendered on client only
146
+ })
147
+ ```
148
+
149
+ ### Functional Form
150
+
151
+ Decide SSR at runtime based on params/search:
152
+
153
+ ```tsx
154
+ export const Route = createFileRoute('/docs/$docType/$docId')({
155
+ ssr: ({ params }) => {
156
+ if (params.status === 'success' && params.value.docType === 'sheet') {
157
+ return false
158
+ }
159
+ },
160
+ })
161
+ ```
162
+
163
+ ### SSR Inheritance
164
+
165
+ Children inherit parent SSR config and can only be MORE restrictive:
166
+
167
+ - `true` → `data-only` or `false` (allowed)
168
+ - `false` → `true` (NOT allowed — parent `false` wins)
169
+
170
+ ### Default SSR
171
+
172
+ Change the default for all routes in `src/start.ts`:
173
+
174
+ ```tsx
175
+ import { createStart } from '@benjavicente/react-start'
176
+
177
+ export const startInstance = createStart(() => ({
178
+ defaultSsr: false,
179
+ }))
180
+ ```
181
+
182
+ ## Static Prerendering
183
+
184
+ Generate static HTML at build time:
185
+
186
+ ```ts
187
+ // vite.config.ts
188
+ tanstackStart({
189
+ prerender: {
190
+ enabled: true,
191
+ crawlLinks: true,
192
+ concurrency: 14,
193
+ failOnError: true,
194
+ },
195
+ })
196
+ ```
197
+
198
+ Static routes are auto-discovered. Dynamic routes (e.g. `/users/$userId`) require `crawlLinks` or explicit `pages` config.
199
+
200
+ ## SEO and Head Management
201
+
202
+ ### Basic Meta Tags
203
+
204
+ ```tsx
205
+ export const Route = createFileRoute('/')({
206
+ head: () => ({
207
+ meta: [
208
+ { title: 'My App - Home' },
209
+ { name: 'description', content: 'Welcome to My App' },
210
+ ],
211
+ }),
212
+ })
213
+ ```
214
+
215
+ ### Dynamic Meta from Loader Data
216
+
217
+ ```tsx
218
+ export const Route = createFileRoute('/posts/$postId')({
219
+ loader: async ({ params }) => fetchPost(params.postId),
220
+ head: ({ loaderData }) => ({
221
+ meta: [
222
+ { title: loaderData.title },
223
+ { name: 'description', content: loaderData.excerpt },
224
+ { property: 'og:title', content: loaderData.title },
225
+ { property: 'og:image', content: loaderData.coverImage },
226
+ ],
227
+ }),
228
+ })
229
+ ```
230
+
231
+ ### Structured Data (JSON-LD)
232
+
233
+ ```tsx
234
+ head: ({ loaderData }) => ({
235
+ scripts: [
236
+ {
237
+ type: 'application/ld+json',
238
+ children: JSON.stringify({
239
+ '@context': 'https://schema.org',
240
+ '@type': 'Article',
241
+ headline: loaderData.title,
242
+ }),
243
+ },
244
+ ],
245
+ })
246
+ ```
247
+
248
+ ### Dynamic Sitemap via Server Route
249
+
250
+ ```ts
251
+ // src/routes/sitemap[.]xml.ts
252
+ export const Route = createFileRoute('/sitemap.xml')({
253
+ server: {
254
+ handlers: {
255
+ GET: async () => {
256
+ const posts = await fetchAllPosts()
257
+ const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
258
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
259
+ ${posts.map((p) => `<url><loc>https://myapp.com/posts/${p.id}</loc></url>`).join('')}
260
+ </urlset>`
261
+ return new Response(sitemap, {
262
+ headers: { 'Content-Type': 'application/xml' },
263
+ })
264
+ },
265
+ },
266
+ },
267
+ })
268
+ ```
269
+
270
+ ## Common Mistakes
271
+
272
+ ### 1. HIGH: Missing nodejs_compat flag for Cloudflare Workers
273
+
274
+ ```jsonc
275
+ // WRONG — Node.js APIs fail at runtime
276
+ { "compatibility_flags": [] }
277
+
278
+ // CORRECT
279
+ { "compatibility_flags": ["nodejs_compat"] }
280
+ ```
281
+
282
+ ### 2. MEDIUM: Bun deployment with React 18
283
+
284
+ Bun-specific deployment only works with React 19. Use Node.js deployment for React 18.
285
+
286
+ ### 3. MEDIUM: Child route loosening parent SSR config
287
+
288
+ ```tsx
289
+ // Parent sets ssr: false
290
+ // WRONG — child cannot upgrade to ssr: true
291
+ const parentRoute = createFileRoute('/dashboard')({ ssr: false })
292
+ const childRoute = createFileRoute('/dashboard/stats')({
293
+ ssr: true, // IGNORED — parent false wins
294
+ })
295
+
296
+ // CORRECT — children can only be MORE restrictive
297
+ const parentRoute = createFileRoute('/dashboard')({ ssr: 'data-only' })
298
+ const childRoute = createFileRoute('/dashboard/stats')({
299
+ ssr: false, // OK — more restrictive than parent
300
+ })
301
+ ```
302
+
303
+ ## Cross-References
304
+
305
+ - [start-core/server-routes](../server-routes/SKILL.md) — API endpoints for sitemaps, robots.txt
306
+ - [start-core/execution-model](../execution-model/SKILL.md) — SSR affects where code runs
@@ -0,0 +1,302 @@
1
+ ---
2
+ name: start-core/execution-model
3
+ description: >-
4
+ Isomorphic-by-default principle, environment boundary functions
5
+ (createServerFn, createServerOnlyFn, createClientOnlyFn,
6
+ createIsomorphicFn), ClientOnly component, useHydrated hook,
7
+ import protection, dead code elimination, environment variable
8
+ safety (VITE_ prefix, process.env).
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/execution-model.md
16
+ - TanStack/router:docs/start/framework/react/guide/environment-variables.md
17
+ ---
18
+
19
+ # Execution Model
20
+
21
+ Understanding where code runs is fundamental to TanStack Start. This skill covers the isomorphic execution model and how to control environment boundaries.
22
+
23
+ > **CRITICAL**: ALL code in TanStack Start is isomorphic by default — it runs in BOTH server and client bundles. Route loaders run on BOTH server (during SSR) AND client (during navigation). Server-only operations MUST use `createServerFn`.
24
+ > **CRITICAL**: Module-level `process.env` access runs in both environments. Secret values leak into the client bundle. Access secrets ONLY inside `createServerFn` or `createServerOnlyFn`.
25
+ > **CRITICAL**: `VITE_` prefixed environment variables are exposed to the client bundle. Server secrets must NOT have the `VITE_` prefix.
26
+
27
+ ## Execution Control APIs
28
+
29
+ | API | Use Case | Client Behavior | Server Behavior |
30
+ | ------------------------ | ------------------------- | ------------------------- | --------------------- |
31
+ | `createServerFn()` | RPC calls, data mutations | Network request to server | Direct execution |
32
+ | `createServerOnlyFn(fn)` | Utility functions | Throws error | Direct execution |
33
+ | `createClientOnlyFn(fn)` | Browser utilities | Direct execution | Throws error |
34
+ | `createIsomorphicFn()` | Different impl per env | Uses `.client()` impl | Uses `.server()` impl |
35
+ | `<ClientOnly>` | Browser-only components | Renders children | Renders fallback |
36
+ | `useHydrated()` | Hydration-dependent logic | `true` after hydration | `false` |
37
+
38
+ ## Server-Only Execution
39
+
40
+ ### createServerFn (RPC pattern)
41
+
42
+ The primary way to run server-only code. On the client, calls become fetch requests:
43
+
44
+ ```tsx
45
+ // Use @tanstack/<framework>-start for your framework (react, solid, vue)
46
+ import { createServerFn } from '@benjavicente/react-start'
47
+
48
+ const fetchUser = createServerFn().handler(async () => {
49
+ const secret = process.env.API_SECRET // safe — server only
50
+ return await db.users.find()
51
+ })
52
+
53
+ // Client calls this via network request
54
+ const user = await fetchUser()
55
+ ```
56
+
57
+ ### createServerOnlyFn (throws on client)
58
+
59
+ For utility functions that must never run on client:
60
+
61
+ ```tsx
62
+ // Use @tanstack/<framework>-start for your framework (react, solid, vue)
63
+ import { createServerOnlyFn } from '@benjavicente/react-start'
64
+
65
+ const getSecret = createServerOnlyFn(() => process.env.DATABASE_URL)
66
+
67
+ // Server: returns the value
68
+ // Client: THROWS an error
69
+ ```
70
+
71
+ ## Client-Only Execution
72
+
73
+ ### createClientOnlyFn
74
+
75
+ ```tsx
76
+ // Use @tanstack/<framework>-start for your framework (react, solid, vue)
77
+ import { createClientOnlyFn } from '@benjavicente/react-start'
78
+
79
+ const saveToStorage = createClientOnlyFn((key: string, value: string) => {
80
+ localStorage.setItem(key, value)
81
+ })
82
+ ```
83
+
84
+ ### ClientOnly Component
85
+
86
+ ```tsx
87
+ // Use @tanstack/<framework>-router for your framework (react, solid, vue)
88
+ import { ClientOnly } from '@benjavicente/react-router'
89
+
90
+ function Analytics() {
91
+ return (
92
+ <ClientOnly fallback={null}>
93
+ <GoogleAnalyticsScript />
94
+ </ClientOnly>
95
+ )
96
+ }
97
+ ```
98
+
99
+ ### useHydrated Hook
100
+
101
+ ```tsx
102
+ // Use @tanstack/<framework>-router for your framework (react, solid, vue)
103
+ import { useHydrated } from '@benjavicente/react-router'
104
+
105
+ function TimeZoneDisplay() {
106
+ const hydrated = useHydrated()
107
+ const timeZone = hydrated
108
+ ? Intl.DateTimeFormat().resolvedOptions().timeZone
109
+ : 'UTC'
110
+
111
+ return <div>Your timezone: {timeZone}</div>
112
+ }
113
+ ```
114
+
115
+ Behavior: SSR → `false`, first client render → `false`, after hydration → `true` (stays `true`).
116
+
117
+ ## Environment-Specific Implementations
118
+
119
+ ```tsx
120
+ // Use @tanstack/<framework>-start for your framework (react, solid, vue)
121
+ import { createIsomorphicFn } from '@benjavicente/react-start'
122
+
123
+ const getDeviceInfo = createIsomorphicFn()
124
+ .server(() => ({ type: 'server', platform: process.platform }))
125
+ .client(() => ({ type: 'client', userAgent: navigator.userAgent }))
126
+ ```
127
+
128
+ ## Environment Variables
129
+
130
+ ### Server-Side (inside createServerFn)
131
+
132
+ Access any variable via `process.env`:
133
+
134
+ ```tsx
135
+ const connectDb = createServerFn().handler(async () => {
136
+ const url = process.env.DATABASE_URL // no prefix needed
137
+ return createConnection(url)
138
+ })
139
+ ```
140
+
141
+ ### Client-Side (components)
142
+
143
+ Only `VITE_` prefixed variables are available:
144
+
145
+ ```tsx
146
+ // Framework-specific component type (React.ReactNode, JSX.Element, etc.)
147
+ function ApiProvider({ children }: { children: React.ReactNode }) {
148
+ const apiUrl = import.meta.env.VITE_API_URL // available
149
+ // import.meta.env.DATABASE_URL → undefined (security)
150
+ return (
151
+ <ApiContext.Provider value={{ apiUrl }}>{children}</ApiContext.Provider>
152
+ )
153
+ }
154
+ ```
155
+
156
+ ### Runtime Client Variables
157
+
158
+ If you need server-side variables on the client without `VITE_` prefix, pass them through a server function:
159
+
160
+ ```tsx
161
+ const getRuntimeVar = createServerFn({ method: 'GET' }).handler(() => {
162
+ return process.env.MY_RUNTIME_VAR
163
+ })
164
+
165
+ export const Route = createFileRoute('/')({
166
+ loader: async () => {
167
+ const foo = await getRuntimeVar()
168
+ return { foo }
169
+ },
170
+ component: () => {
171
+ const { foo } = Route.useLoaderData()
172
+ return <div>{foo}</div>
173
+ },
174
+ })
175
+ ```
176
+
177
+ ### Type Safety for Environment Variables
178
+
179
+ ```tsx
180
+ // src/env.d.ts
181
+ /// <reference types="vite/client" />
182
+
183
+ interface ImportMetaEnv {
184
+ readonly VITE_APP_NAME: string
185
+ readonly VITE_API_URL: string
186
+ }
187
+
188
+ interface ImportMeta {
189
+ readonly env: ImportMetaEnv
190
+ }
191
+
192
+ declare global {
193
+ namespace NodeJS {
194
+ interface ProcessEnv {
195
+ readonly DATABASE_URL: string
196
+ readonly JWT_SECRET: string
197
+ }
198
+ }
199
+ }
200
+
201
+ export {}
202
+ ```
203
+
204
+ ## Common Mistakes
205
+
206
+ ### 1. CRITICAL: Assuming loaders are server-only
207
+
208
+ ```tsx
209
+ // WRONG — loader runs on BOTH server and client
210
+ export const Route = createFileRoute('/dashboard')({
211
+ loader: async () => {
212
+ const secret = process.env.API_SECRET // LEAKED to client
213
+ return fetch(`https://api.example.com/data`, {
214
+ headers: { Authorization: secret },
215
+ })
216
+ },
217
+ })
218
+
219
+ // CORRECT — use createServerFn
220
+ const getData = createServerFn({ method: 'GET' }).handler(async () => {
221
+ const secret = process.env.API_SECRET
222
+ return fetch(`https://api.example.com/data`, {
223
+ headers: { Authorization: secret },
224
+ })
225
+ })
226
+
227
+ export const Route = createFileRoute('/dashboard')({
228
+ loader: () => getData(),
229
+ })
230
+ ```
231
+
232
+ ### 2. CRITICAL: Exposing secrets via module-level process.env
233
+
234
+ ```tsx
235
+ // WRONG — runs in both environments, value in client bundle
236
+ const apiKey = process.env.SECRET_KEY
237
+ export function fetchData() {
238
+ /* uses apiKey */
239
+ }
240
+
241
+ // CORRECT — access inside server function only
242
+ const fetchData = createServerFn({ method: 'GET' }).handler(async () => {
243
+ const apiKey = process.env.SECRET_KEY
244
+ return fetch(url, { headers: { Authorization: apiKey } })
245
+ })
246
+ ```
247
+
248
+ ### 3. CRITICAL: Using VITE\_ prefix for server secrets
249
+
250
+ ```bash
251
+ # WRONG — exposed to client bundle
252
+ VITE_SECRET_API_KEY=sk_live_xxx
253
+
254
+ # CORRECT — no prefix for server secrets
255
+ SECRET_API_KEY=sk_live_xxx
256
+
257
+ # CORRECT — VITE_ only for public client values
258
+ VITE_APP_NAME=My App
259
+ ```
260
+
261
+ ### 4. HIGH: Hydration mismatches
262
+
263
+ ```tsx
264
+ // WRONG — different content server vs client
265
+ function CurrentTime() {
266
+ return <div>{new Date().toLocaleString()}</div>
267
+ }
268
+
269
+ // CORRECT — consistent rendering
270
+ function CurrentTime() {
271
+ const [time, setTime] = useState<string>()
272
+ useEffect(() => {
273
+ setTime(new Date().toLocaleString())
274
+ }, [])
275
+ return <div>{time || 'Loading...'}</div>
276
+ }
277
+ ```
278
+
279
+ ## Architecture Decision Framework
280
+
281
+ **Server-Only** (`createServerFn` / `createServerOnlyFn`):
282
+
283
+ - Sensitive data (env vars, secrets)
284
+ - Database connections, file system
285
+ - External API keys
286
+
287
+ **Client-Only** (`createClientOnlyFn` / `<ClientOnly>`):
288
+
289
+ - DOM manipulation, browser APIs
290
+ - localStorage, geolocation
291
+ - Analytics/tracking
292
+
293
+ **Isomorphic** (default / `createIsomorphicFn`):
294
+
295
+ - Data formatting, business logic
296
+ - Shared utilities
297
+ - Route loaders (they're isomorphic by nature)
298
+
299
+ ## Cross-References
300
+
301
+ - [start-core/server-functions](../server-functions/SKILL.md) — the primary server boundary
302
+ - [start-core/deployment](../deployment/SKILL.md) — deployment target affects execution