Use when adding new error messages to React, or seeing "unknown error code" warnings.
npx skills add DonggangChen/antigravity-agentic-skills --skill "supabase_patterns"
Install specific skill from multi-skill repository
# 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.
# SKILL.md
name: supabase_patterns
router_kit: FullStackKit
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.
metadata:
skillport:
category: auto-healed
tags: [architecture, auth, automation, backend-as-a-service, best practices, clean code, coding, collaboration, compliance, debugging, design patterns, development, documentation, edge functions, efficiency, git, optimization, postgres, productivity, programming, project management, quality assurance, realtime, refactoring, software engineering, standards, supabase patterns, testing, utilities, version control, workflow]
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
// 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
Pattern 2: Client Component with Interactivity
When: User interactions, state management
File location: components/**/*.tsx
// 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
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
// 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
Pattern 4: Server Action for Mutations
When: Form submissions, data mutations
File location: app/actions.ts or colocated with page
// 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
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
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()notgetSession()for auth validation? - [ ] No sensitive data exposed to client?
- [ ] RLS policies considered for data access?
See: supabase-security.md
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
🔄 Workflow
Source: Supabase SSR Guide (Next.js) & Next.js 15 App Router Security
Phase 1: Environment & Client Initialization
- [ ] Client Selection: Initialize Server Client (RSC/Actions) or Browser Client (RCC) using
@supabase/ssras needed. - [ ] Middleware Guard: In addition to RLS (Row Level Security), set up session control and session refresh mechanism in Middleware.
- [ ] Type Mapping: Automatically generate
database.types.tsfile and inject into Supabase instance.
Phase 2: Data Orchestration
- [ ] Server-Side Fetching: Fetch data at top level (Page/Layout) Server Component and pass to child components (RCC) with minimum data.
- [ ] Server Actions: Define secure actions with
use serverdirective for form mutations and update cache withrevalidatePath. - [ ] RLS Audit: Verify that
auth.uid()based policies are active for all tables (SELECT/INSERT/UPDATE) at database level.
Phase 3: Security & Real-time Ops
- [ ] Auth Validation: Always prefer safer
getUser()method overgetSession(). - [ ] Real-time Handling: Set up and clean (
unmount) subscriptions viasupabase.channel()to reflect changes instantly. - [ ] Edge Functions: Use Edge Functions for intensive operations or 3rd party API integration (e.g. Stripe/SendGrid).
Checkpoints
| Phase | Verification |
|---|---|
| 1 | Is Service Role Key ever used in client-side code (RCC)? |
| 2 | Are "Optimistic UI" updates performed after form submit? |
| 3 | Are process.env data compatible in both local and production (Supabase Dashboard) environments? |
Supabase Patterns v2.0 - With Workflow
Getting Help
For detailed patterns and examples:
- Next.js patterns: See nextjs-patterns.md
- Supabase security: See supabase-security.md
- TypeScript types: See typescript-standards.md
- What not to do: See anti-patterns.md
Quick Reference
Server Component template:
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:
'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:
'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 }
}
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.