Back to skills
Nextjs Supabase Patterns
Guides implementation of Next.js 15 App Router features with Supabase SSR. Helps choose between Server/Client Components, select correct Supabase client, and follow security patterns. Use when building pages, components, or API routes.
3 stars
0 votes
0 copies
0 views
Added 12/19/2025
data-aitypescriptreactnextjsapidatabasesecurity
Works with
cliapi
Install via CLI
$
openskills install lifegenieai/claude-skillsFiles
SKILL.md
---
name: Implementing Next.js with Supabase
description: Guides implementation of Next.js 15 App Router features with Supabase SSR. Helps choose between Server/Client Components, select correct Supabase client, and follow security patterns. Use when building pages, components, or API routes.
---
# Implementing Next.js with Supabase
Interactive guide for implementing features using patterns from `.claude/modules/nextjs-patterns.md` and `.claude/modules/supabase-security.md`.
## Quick Decision: Server or Client Component?
Use this decision tree to choose the right component type:
**Step 1: Does it need user interaction?**
- onClick, onChange, onSubmit handlers?
- useState, useEffect, or other React hooks?
- Browser APIs (localStorage, window)?
→ **YES** = Client Component (`'use client'`)
→ **NO** = Continue to Step 2
**Step 2: Does it fetch data from database/API?**
- Supabase queries?
- Fetch from external API?
- Read from database?
→ **YES** = Server Component (default)
→ **NO** = Server Component (default, unless Step 1 was yes)
## Common Scenarios
| What You're Building | Component Type | Supabase Client |
|---------------------|----------------|-----------------|
| Page that displays recipes | Server Component | `@/lib/supabase/server` |
| Save/delete button | Client Component | `@/lib/supabase/client` |
| Form with validation | Client Component | `@/lib/supabase/client` |
| Form with Server Action | Server Component | Use Server Action |
| Real-time chat | Client Component | `@/lib/supabase/client` |
| Dashboard with data | Server Component | `@/lib/supabase/server` |
| API route handler | Server (Route Handler) | `@/lib/supabase/server` |
## Implementation Patterns
### Pattern 1: Server Component with Data Fetching
**When:** Displaying data from database
**File location:** `app/**/page.tsx` or `app/**/layout.tsx`
```typescript
// app/recipes/page.tsx
import { createClient } from '@/lib/supabase/server'
import RecipeList from '@/components/RecipeList'
export default async function RecipesPage() {
const supabase = await createClient()
// Fetch data on server
const { data: recipes, error } = await supabase
.from('saved_recipes')
.select('*')
// Handle errors
if (error) {
return <ErrorDisplay message="Failed to load recipes" />
}
// Pass data to components
return <RecipeList recipes={recipes} />
}
```
**See:** [nextjs-patterns.md](../../modules/nextjs-patterns.md#data-fetching-patterns)
### Pattern 2: Client Component with Interactivity
**When:** User interactions, state management
**File location:** `components/**/*.tsx`
```typescript
// components/SaveButton.tsx
'use client'
import { createClient } from '@/lib/supabase/client'
import { useState } from 'react'
export default function SaveButton({ recipeId }: { recipeId: string }) {
const [saving, setSaving] = useState(false)
const supabase = createClient()
const handleSave = async () => {
setSaving(true)
const { error } = await supabase
.from('saved_recipes')
.insert({ id: recipeId })
if (error) {
console.error('Save failed:', error)
}
setSaving(false)
}
return (
<button onClick={handleSave} disabled={saving}>
{saving ? 'Saving...' : 'Save Recipe'}
</button>
)
}
```
**See:** [nextjs-patterns.md](../../modules/nextjs-patterns.md#client-component-interactivity)
### Pattern 3: Server Component → Client Component (Data Flow)
**When:** Need to pass server data to interactive components
**Rule:** Only pass required fields, never full objects
```typescript
// app/profile/page.tsx (Server Component)
import { createClient } from '@/lib/supabase/server'
import ProfileEditor from '@/components/ProfileEditor' // Client Component
export default async function ProfilePage() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
redirect('/login')
}
// ✅ CORRECT: Pass only required fields
return (
<ProfileEditor
userId={user.id}
email={user.email}
name={user.user_metadata?.name}
/>
)
// ❌ WRONG: Don't pass full user object (security risk)
// return <ProfileEditor user={user} />
}
```
**See:** [supabase-security.md](../../modules/supabase-security.md#data-security-patterns)
### Pattern 4: Server Action for Mutations
**When:** Form submissions, data mutations
**File location:** `app/actions.ts` or colocated with page
```typescript
// app/actions.ts
'use server'
import { createClient } from '@/lib/supabase/server'
import { revalidatePath } from 'next/cache'
export async function saveRecipe(formData: FormData) {
const supabase = await createClient()
// Validate user
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return { error: 'Unauthorized' }
}
// Extract form data
const name = formData.get('name') as string
const ingredients = formData.get('ingredients') as string
// Perform mutation
const { error } = await supabase
.from('saved_recipes')
.insert({ name, ingredients, user_id: user.id })
if (error) {
return { error: error.message }
}
// Revalidate cache
revalidatePath('/recipes')
return { success: true }
}
```
**See:** [nextjs-patterns.md](../../modules/nextjs-patterns.md#server-actions)
## Which Supabase Client?
**Critical rule:** Use the correct client for the environment.
| Environment | Import | Usage |
|------------|--------|-------|
| Server Component | `import { createClient } from '@/lib/supabase/server'` | `const supabase = await createClient()` |
| Client Component | `import { createClient } from '@/lib/supabase/client'` | `const supabase = createClient()` |
| Server Action | `import { createClient } from '@/lib/supabase/server'` | `const supabase = await createClient()` |
| Route Handler | `import { createClient } from '@/lib/supabase/server'` | `const supabase = await createClient()` |
| Middleware | `import { createClient } from '@/lib/supabase/middleware'` | `const { supabase } = createClient(request)` |
**See:** [supabase-security.md](../../modules/supabase-security.md#supabase-client-creation)
## Security Checklist
Before implementing, verify:
- [ ] Using correct Supabase client for environment?
- [ ] Server Component for data fetching (not Client)?
- [ ] Only passing required fields to Client Components?
- [ ] Using `getUser()` not `getSession()` for auth validation?
- [ ] No sensitive data exposed to client?
- [ ] RLS policies considered for data access?
**See:** [supabase-security.md](../../modules/supabase-security.md#critical-security-rules)
## Common Mistakes to Avoid
| ❌ Mistake | ✅ Fix |
|-----------|--------|
| Async Client Component | Use Server Component or `useEffect` |
| Wrong Supabase client | Check table above |
| Passing full user object | Pass only needed fields |
| `any` types | Use specific types from `lib/supabase/types.ts` |
| No error handling | Always check for errors |
| Missing loading states | Show spinner/skeleton |
**See:** [anti-patterns.md](../../modules/anti-patterns.md)
## Implementation Workflow
When building a new feature:
1. **Determine component type** (use decision tree above)
2. **Select Supabase client** (use table above)
3. **Implement following patterns** (see Pattern 1-4 above)
4. **Review security checklist** (see checklist above)
5. **Test with real data**
6. **Run pre-commit checks** (use pre-commit-quality skill)
## Getting Help
For detailed patterns and examples:
- **Next.js patterns:** See [nextjs-patterns.md](../../modules/nextjs-patterns.md)
- **Supabase security:** See [supabase-security.md](../../modules/supabase-security.md)
- **TypeScript types:** See [typescript-standards.md](../../modules/typescript-standards.md)
- **What not to do:** See [anti-patterns.md](../../modules/anti-patterns.md)
## Quick Reference
**Server Component template:**
```typescript
import { createClient } from '@/lib/supabase/server'
export default async function Page() {
const supabase = await createClient()
const { data, error } = await supabase.from('table').select('*')
if (error) return <Error />
return <Component data={data} />
}
```
**Client Component template:**
```typescript
'use client'
import { createClient } from '@/lib/supabase/client'
import { useState } from 'react'
export default function Component() {
const [state, setState] = useState()
const supabase = createClient()
// Your interactive logic
return <div onClick={handler}>...</div>
}
```
**Server Action template:**
```typescript
'use server'
import { createClient } from '@/lib/supabase/server'
import { revalidatePath } from 'next/cache'
export async function action(formData: FormData) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return { error: 'Unauthorized' }
// Mutation logic
revalidatePath('/path')
return { success: true }
}
```
Attribution
Comments (0)
No comments yet. Be the first to comment!
