Skip to content

Commit 761e783

Browse files
docs(examples): add serialization documentation to Supabase example
Add comprehensive documentation explaining server function serialization requirements when using Supabase with TanStack Start. Changes: - Add README.md with setup guide and serialization best practices - Add 40+ line JSDoc comment in __root.tsx explaining serialization constraints - Improve error handling (log errors instead of suppressing with _error) - Add inline examples showing correct vs incorrect patterns - Add commented examples for safely adding more user fields Fixes #3831 The code in the Supabase example is functionally correct, but lacked documentation explaining the serialization requirements for server functions. This caused confusion for developers who wanted to return additional user fields from Supabase. This PR helps developers understand: - Why only primitive values can be returned from server functions - What Supabase user objects contain (non-serializable data) - How to safely add more fields - Common errors and their solutions Testing: - Verified with real Supabase credentials - Confirmed no hydration errors exist - Confirmed no serialization errors in current implementation - Code pattern is correct, documentation was missing 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a219007 commit 761e783

File tree

2 files changed

+245
-1
lines changed

2 files changed

+245
-1
lines changed
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
# TanStack Start + Supabase Basic Example
2+
3+
This example demonstrates how to integrate Supabase authentication with TanStack Start.
4+
5+
## Setup
6+
7+
1. Create a Supabase project at https://supabase.com
8+
2. Copy `.env` and fill in your Supabase credentials:
9+
```
10+
SUPABASE_URL=your-project-url
11+
SUPABASE_ANON_KEY=your-anon-key
12+
```
13+
3. Install dependencies: `npm install`
14+
4. Run: `npm run dev`
15+
16+
## ⚠️ Important: Server Function Serialization
17+
18+
**CRITICAL**: Server functions in TanStack Start can only return serializable data.
19+
20+
### The Problem
21+
22+
Supabase returns rich objects with non-serializable properties:
23+
24+
```typescript
25+
const { data } = await supabase.auth.getUser()
26+
// data.user contains functions, metadata, internal state
27+
```
28+
29+
### The Solution
30+
31+
Extract only primitive values:
32+
33+
```typescript
34+
CORRECT:
35+
return {
36+
email: data.user.email, // string ✅
37+
id: data.user.id, // string ✅
38+
role: data.user.role, // string ✅
39+
}
40+
41+
WRONG:
42+
return data.user // Contains functions and metadata ❌
43+
```
44+
45+
### What's Serializable?
46+
47+
| Type | Serializable? | Example |
48+
|------|---------------|---------|
49+
| String | ✅ Yes | `"hello"` |
50+
| Number | ✅ Yes | `42` |
51+
| Boolean | ✅ Yes | `true` |
52+
| null | ✅ Yes | `null` |
53+
| Plain Object | ✅ Yes | `{ name: "Alice" }` |
54+
| Array | ✅ Yes | `[1, 2, 3]` |
55+
| ISO String | ✅ Yes | `new Date().toISOString()` |
56+
| undefined | ❌ No | Use `null` instead |
57+
| Function | ❌ No | Cannot serialize |
58+
| Class Instance | ❌ No | Extract fields |
59+
| Date Object | ❌ No | Convert to ISO string |
60+
61+
## Common Errors & Solutions
62+
63+
### Error: "Cannot serialize function"
64+
**Cause**: Returning an object with methods
65+
**Fix**: Extract only primitive values
66+
67+
```typescript
68+
// ❌ Wrong
69+
return data.user
70+
71+
// ✅ Correct
72+
return {
73+
email: data.user.email,
74+
id: data.user.id,
75+
}
76+
```
77+
78+
### Error: "Cannot serialize undefined"
79+
**Cause**: A field is `undefined` instead of `null`
80+
**Fix**: Use `null` or omit the field entirely
81+
82+
```typescript
83+
// ❌ Wrong
84+
return {
85+
name: data.user.name, // might be undefined
86+
}
87+
88+
// ✅ Correct
89+
return {
90+
name: data.user.name ?? null, // convert undefined to null
91+
}
92+
```
93+
94+
### Error: React Hydration Mismatch
95+
**Cause**: Server and client render different HTML
96+
**Fix**: Ensure consistent data structure between server/client
97+
98+
## Project Structure
99+
100+
```
101+
src/
102+
├── routes/
103+
│ ├── __root.tsx # Root layout with user fetching
104+
│ ├── _authed.tsx # Protected route layout
105+
│ ├── index.tsx # Home page
106+
│ ├── login.tsx # Login page
107+
│ ├── signup.tsx # Signup page
108+
│ └── _authed/
109+
│ └── posts.$postId.tsx
110+
├── components/
111+
│ ├── Auth.tsx # Authentication UI
112+
│ └── Login.tsx
113+
├── utils/
114+
│ ├── supabase.ts # Supabase client config
115+
│ └── posts.ts
116+
└── styles/
117+
└── app.css
118+
```
119+
120+
## How It Works
121+
122+
### 1. User Authentication Check (`__root.tsx`)
123+
The root route uses a server function to check authentication:
124+
125+
```typescript
126+
const fetchUser = createServerFn({ method: 'GET' }).handler(async () => {
127+
const supabase = getSupabaseServerClient()
128+
const { data, error } = await supabase.auth.getUser()
129+
130+
if (error || !data.user?.email) {
131+
return null
132+
}
133+
134+
// IMPORTANT: Only return serializable fields!
135+
return {
136+
email: data.user.email,
137+
}
138+
})
139+
```
140+
141+
### 2. Protected Routes (`_authed.tsx`)
142+
Routes prefixed with `_authed` redirect unauthenticated users to login:
143+
144+
```typescript
145+
beforeLoad: ({ context }) => {
146+
if (!context.user) {
147+
throw redirect({ to: '/login' })
148+
}
149+
}
150+
```
151+
152+
### 3. Supabase Client (`utils/supabase.ts`)
153+
Server-side Supabase client with cookie handling:
154+
155+
```typescript
156+
export function getSupabaseServerClient() {
157+
return createServerClient(
158+
process.env.SUPABASE_URL!,
159+
process.env.SUPABASE_ANON_KEY!,
160+
{
161+
cookies: {
162+
getAll() { /* ... */ },
163+
setAll(cookies) { /* ... */ },
164+
},
165+
},
166+
)
167+
}
168+
```
169+
170+
## Learn More
171+
172+
- [TanStack Start Documentation](https://tanstack.com/start/latest)
173+
- [TanStack Start Server Functions](https://tanstack.com/start/latest/docs/framework/react/server-functions)
174+
- [Supabase Auth Documentation](https://supabase.com/docs/guides/auth)
175+
- [Supabase SSR Guide](https://supabase.com/docs/guides/auth/server-side-rendering)
176+
177+
## Troubleshooting
178+
179+
### Authentication not persisting
180+
Make sure cookies are properly configured in `utils/supabase.ts`
181+
182+
### TypeScript errors
183+
Run `npm run build` to check for type errors
184+
185+
### Can't connect to Supabase
186+
Verify your `.env` file has correct credentials
187+
188+
## Example Deployment
189+
190+
This example works with:
191+
- ✅ Cloudflare Pages
192+
- ✅ Netlify
193+
- ✅ Vercel
194+
- ✅ Node.js servers
195+
196+
See deployment guides in the TanStack Start documentation.

examples/react/start-supabase-basic/src/routes/__root.tsx

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,64 @@ import appCss from '../styles/app.css?url'
1515
import { seo } from '../utils/seo'
1616
import { getSupabaseServerClient } from '../utils/supabase'
1717

18+
/**
19+
* ⚠️ IMPORTANT: Server Function Serialization Requirements
20+
*
21+
* Server functions in TanStack Start can ONLY return serializable data.
22+
* This means data that can be converted to JSON and sent to the client.
23+
*
24+
* Supabase's `data.user` object contains NON-serializable properties:
25+
* - Functions (e.g., user.toString, internal methods)
26+
* - Complex metadata objects with circular references
27+
* - Internal Supabase client state
28+
*
29+
* ❌ WRONG - This will cause "Cannot serialize function" errors:
30+
* ```
31+
* return data.user // Contains functions and complex objects
32+
* ```
33+
*
34+
* ✅ CORRECT - Extract only primitive values:
35+
* ```
36+
* return {
37+
* email: data.user.email, // string ✅
38+
* id: data.user.id, // string ✅
39+
* role: data.user.role, // string ✅
40+
* }
41+
* ```
42+
*
43+
* What's serializable?
44+
* - ✅ Primitives: string, number, boolean, null
45+
* - ✅ Plain objects: { key: value }
46+
* - ✅ Arrays: [1, 2, 3]
47+
* - ❌ Functions, class instances, undefined
48+
* - ❌ Date objects (convert to ISO string: date.toISOString())
49+
*
50+
* Learn more:
51+
* - Server Functions: https://tanstack.com/router/latest/docs/framework/react/start/server-functions
52+
* - Supabase SSR: https://supabase.com/docs/guides/auth/server-side-rendering
53+
*/
1854
const fetchUser = createServerFn({ method: 'GET' }).handler(async () => {
1955
const supabase = getSupabaseServerClient()
20-
const { data, error: _error } = await supabase.auth.getUser()
56+
const { data, error } = await supabase.auth.getUser()
57+
58+
// Always handle errors explicitly - don't suppress them
59+
if (error) {
60+
console.error('[fetchUser] Supabase auth error:', error.message)
61+
return null
62+
}
2163

2264
if (!data.user?.email) {
2365
return null
2466
}
2567

68+
// IMPORTANT: Only return serializable fields from the user object
69+
// Add more fields here as needed (all must be primitives or plain objects)
2670
return {
2771
email: data.user.email,
72+
// You can safely add more primitive fields:
73+
// id: data.user.id,
74+
// role: data.user.role,
75+
// name: data.user.user_metadata?.name ?? null,
2876
}
2977
})
3078

0 commit comments

Comments
 (0)