From 761e783a1a69b6dfb163937eb3bf129429fd4410 Mon Sep 17 00:00:00 2001 From: PaulyBearCoding Date: Tue, 28 Oct 2025 22:17:34 -0700 Subject: [PATCH] docs(examples): add serialization documentation to Supabase example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- examples/react/start-supabase-basic/README.md | 196 ++++++++++++++++++ .../src/routes/__root.tsx | 50 ++++- 2 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 examples/react/start-supabase-basic/README.md diff --git a/examples/react/start-supabase-basic/README.md b/examples/react/start-supabase-basic/README.md new file mode 100644 index 00000000000..f69185a7c99 --- /dev/null +++ b/examples/react/start-supabase-basic/README.md @@ -0,0 +1,196 @@ +# TanStack Start + Supabase Basic Example + +This example demonstrates how to integrate Supabase authentication with TanStack Start. + +## Setup + +1. Create a Supabase project at https://supabase.com +2. Copy `.env` and fill in your Supabase credentials: + ``` + SUPABASE_URL=your-project-url + SUPABASE_ANON_KEY=your-anon-key + ``` +3. Install dependencies: `npm install` +4. Run: `npm run dev` + +## ⚠️ Important: Server Function Serialization + +**CRITICAL**: Server functions in TanStack Start can only return serializable data. + +### The Problem + +Supabase returns rich objects with non-serializable properties: + +```typescript +const { data } = await supabase.auth.getUser() +// data.user contains functions, metadata, internal state +``` + +### The Solution + +Extract only primitive values: + +```typescript +✅ CORRECT: +return { + email: data.user.email, // string ✅ + id: data.user.id, // string ✅ + role: data.user.role, // string ✅ +} + +❌ WRONG: +return data.user // Contains functions and metadata ❌ +``` + +### What's Serializable? + +| Type | Serializable? | Example | +|------|---------------|---------| +| String | ✅ Yes | `"hello"` | +| Number | ✅ Yes | `42` | +| Boolean | ✅ Yes | `true` | +| null | ✅ Yes | `null` | +| Plain Object | ✅ Yes | `{ name: "Alice" }` | +| Array | ✅ Yes | `[1, 2, 3]` | +| ISO String | ✅ Yes | `new Date().toISOString()` | +| undefined | ❌ No | Use `null` instead | +| Function | ❌ No | Cannot serialize | +| Class Instance | ❌ No | Extract fields | +| Date Object | ❌ No | Convert to ISO string | + +## Common Errors & Solutions + +### Error: "Cannot serialize function" +**Cause**: Returning an object with methods +**Fix**: Extract only primitive values + +```typescript +// ❌ Wrong +return data.user + +// ✅ Correct +return { + email: data.user.email, + id: data.user.id, +} +``` + +### Error: "Cannot serialize undefined" +**Cause**: A field is `undefined` instead of `null` +**Fix**: Use `null` or omit the field entirely + +```typescript +// ❌ Wrong +return { + name: data.user.name, // might be undefined +} + +// ✅ Correct +return { + name: data.user.name ?? null, // convert undefined to null +} +``` + +### Error: React Hydration Mismatch +**Cause**: Server and client render different HTML +**Fix**: Ensure consistent data structure between server/client + +## Project Structure + +``` +src/ +├── routes/ +│ ├── __root.tsx # Root layout with user fetching +│ ├── _authed.tsx # Protected route layout +│ ├── index.tsx # Home page +│ ├── login.tsx # Login page +│ ├── signup.tsx # Signup page +│ └── _authed/ +│ └── posts.$postId.tsx +├── components/ +│ ├── Auth.tsx # Authentication UI +│ └── Login.tsx +├── utils/ +│ ├── supabase.ts # Supabase client config +│ └── posts.ts +└── styles/ + └── app.css +``` + +## How It Works + +### 1. User Authentication Check (`__root.tsx`) +The root route uses a server function to check authentication: + +```typescript +const fetchUser = createServerFn({ method: 'GET' }).handler(async () => { + const supabase = getSupabaseServerClient() + const { data, error } = await supabase.auth.getUser() + + if (error || !data.user?.email) { + return null + } + + // IMPORTANT: Only return serializable fields! + return { + email: data.user.email, + } +}) +``` + +### 2. Protected Routes (`_authed.tsx`) +Routes prefixed with `_authed` redirect unauthenticated users to login: + +```typescript +beforeLoad: ({ context }) => { + if (!context.user) { + throw redirect({ to: '/login' }) + } +} +``` + +### 3. Supabase Client (`utils/supabase.ts`) +Server-side Supabase client with cookie handling: + +```typescript +export function getSupabaseServerClient() { + return createServerClient( + process.env.SUPABASE_URL!, + process.env.SUPABASE_ANON_KEY!, + { + cookies: { + getAll() { /* ... */ }, + setAll(cookies) { /* ... */ }, + }, + }, + ) +} +``` + +## Learn More + +- [TanStack Start Documentation](https://tanstack.com/start/latest) +- [TanStack Start Server Functions](https://tanstack.com/start/latest/docs/framework/react/server-functions) +- [Supabase Auth Documentation](https://supabase.com/docs/guides/auth) +- [Supabase SSR Guide](https://supabase.com/docs/guides/auth/server-side-rendering) + +## Troubleshooting + +### Authentication not persisting +Make sure cookies are properly configured in `utils/supabase.ts` + +### TypeScript errors +Run `npm run build` to check for type errors + +### Can't connect to Supabase +Verify your `.env` file has correct credentials + +## Example Deployment + +This example works with: +- ✅ Cloudflare Pages +- ✅ Netlify +- ✅ Vercel +- ✅ Node.js servers + +See deployment guides in the TanStack Start documentation. diff --git a/examples/react/start-supabase-basic/src/routes/__root.tsx b/examples/react/start-supabase-basic/src/routes/__root.tsx index 551c57e8216..342a6bbaeda 100644 --- a/examples/react/start-supabase-basic/src/routes/__root.tsx +++ b/examples/react/start-supabase-basic/src/routes/__root.tsx @@ -15,16 +15,64 @@ import appCss from '../styles/app.css?url' import { seo } from '../utils/seo' import { getSupabaseServerClient } from '../utils/supabase' +/** + * ⚠️ IMPORTANT: Server Function Serialization Requirements + * + * Server functions in TanStack Start can ONLY return serializable data. + * This means data that can be converted to JSON and sent to the client. + * + * Supabase's `data.user` object contains NON-serializable properties: + * - Functions (e.g., user.toString, internal methods) + * - Complex metadata objects with circular references + * - Internal Supabase client state + * + * ❌ WRONG - This will cause "Cannot serialize function" errors: + * ``` + * return data.user // Contains functions and complex objects + * ``` + * + * ✅ CORRECT - Extract only primitive values: + * ``` + * return { + * email: data.user.email, // string ✅ + * id: data.user.id, // string ✅ + * role: data.user.role, // string ✅ + * } + * ``` + * + * What's serializable? + * - ✅ Primitives: string, number, boolean, null + * - ✅ Plain objects: { key: value } + * - ✅ Arrays: [1, 2, 3] + * - ❌ Functions, class instances, undefined + * - ❌ Date objects (convert to ISO string: date.toISOString()) + * + * Learn more: + * - Server Functions: https://tanstack.com/router/latest/docs/framework/react/start/server-functions + * - Supabase SSR: https://supabase.com/docs/guides/auth/server-side-rendering + */ const fetchUser = createServerFn({ method: 'GET' }).handler(async () => { const supabase = getSupabaseServerClient() - const { data, error: _error } = await supabase.auth.getUser() + const { data, error } = await supabase.auth.getUser() + + // Always handle errors explicitly - don't suppress them + if (error) { + console.error('[fetchUser] Supabase auth error:', error.message) + return null + } if (!data.user?.email) { return null } + // IMPORTANT: Only return serializable fields from the user object + // Add more fields here as needed (all must be primitives or plain objects) return { email: data.user.email, + // You can safely add more primitive fields: + // id: data.user.id, + // role: data.user.role, + // name: data.user.user_metadata?.name ?? null, } })