You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+91Lines changed: 91 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -72,3 +72,94 @@ loader: async ({ deps }) => {
72
72
```
73
73
74
74
This ensures the loader only re-runs when the specific dependencies change, not when unrelated search params (like `expanded`, `viewMode`, etc.) change.
75
+
76
+
### Loaders Are Isomorphic
77
+
78
+
**Loaders in TanStack Start/Router are isomorphic and cannot call server logic unless via a call to a server function.**
79
+
80
+
Loaders run on both the server and client, so they cannot directly access server-only APIs (like file system, database connections, etc.). To perform server-side operations, loaders must call server functions (e.g., TanStack server functions created via `createServerFn()`, Convex queries/mutations, API routes, or other server functions).
81
+
82
+
❌ **Bad:**
83
+
84
+
```typescript
85
+
loader: async () => {
86
+
// This won't work - direct server API access
87
+
const data =awaitfs.readFile('data.json')
88
+
return { data }
89
+
}
90
+
```
91
+
92
+
✅ **Good:**
93
+
94
+
```typescript
95
+
loader: async () => {
96
+
// Call a server function instead (TanStack server function or Convex)
97
+
// TanStack server functions created via createServerFn() can be called directly
98
+
const data =awaitserverFn({ data: { id: '123' } })
99
+
// or
100
+
const data =awaitconvex.query(api.data.getData)
101
+
return { data }
102
+
}
103
+
```
104
+
105
+
## Server-Only Code and Environment Shaking
106
+
107
+
### TanStack Start Environment Shaking
108
+
109
+
**TanStack Start performs environment shaking - any code not referenced by a `createServerFn` handler is stripped from the client build.**
110
+
111
+
This means:
112
+
113
+
- Server-only code (database, file system, etc.) is automatically excluded from client bundles
114
+
- Only code inside `createServerFn` handlers is included in server bundles
115
+
- Code outside handlers is included in both server and client bundles
116
+
117
+
### Importing Server Functions
118
+
119
+
**Server functions wrapped in `createServerFn` can be safely imported statically in route files.**
This causes bundler issues because dynamic imports can't be properly tree-shaken, potentially pulling server-only code (like `Buffer`, `drizzle`, `postgres`) into the client bundle.
134
+
135
+
✅ **Good - Static imports:**
136
+
137
+
```typescript
138
+
// At the top of the route file
139
+
import { listRoles } from'~/utils/roles.server'
140
+
141
+
// In component code
142
+
const rolesQuery =useQuery({
143
+
queryFn: async () => {
144
+
returnlistRoles({ data: {} })
145
+
},
146
+
})
147
+
```
148
+
149
+
Since `listRoles` is wrapped in `createServerFn`, TanStack Start will properly handle environment shaking and exclude server-only dependencies from the client bundle.
150
+
151
+
### Rules for Server-Only Imports
152
+
153
+
1.**Server functions** (`createServerFn` wrappers) can be imported statically anywhere
154
+
2.**Direct server-only code** (database clients, file system, etc.) must ONLY be imported:
155
+
- Inside `createServerFn` handlers
156
+
- In separate server-only files (e.g., `*.server.ts`)
157
+
- Never use dynamic imports (`await import()`) for server-only code in component code
158
+
159
+
## Development & Build Commands
160
+
161
+
### Use `build` for Testing Build Output
162
+
163
+
**The `dev` command does not end - it runs indefinitely in watch mode.**
164
+
165
+
When agents need to test build output or verify that the project builds successfully, use the `build` command instead of `dev`. The `build` command will complete and exit, making it suitable for automated testing and verification.
0 commit comments