Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions examples/react/start-supabase-basic/README.md
Original file line number Diff line number Diff line change
@@ -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.
50 changes: 49 additions & 1 deletion examples/react/start-supabase-basic/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
})

Expand Down