Get the FREE Ultimate OpenClaw Setup Guide →

Implementing Next.js with Supabase

Scanned
npx machina-cli add skill Hildegaardchiasmal966/claude-skills/nextjs-supabase-patterns --openclaw
Files (1)
SKILL.md
8.8 KB

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 BuildingComponent TypeSupabase Client
Page that displays recipesServer Component@/lib/supabase/server
Save/delete buttonClient Component@/lib/supabase/client
Form with validationClient Component@/lib/supabase/client
Form with Server ActionServer ComponentUse Server Action
Real-time chatClient Component@/lib/supabase/client
Dashboard with dataServer Component@/lib/supabase/server
API route handlerServer (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.

EnvironmentImportUsage
Server Componentimport { createClient } from '@/lib/supabase/server'const supabase = await createClient()
Client Componentimport { createClient } from '@/lib/supabase/client'const supabase = createClient()
Server Actionimport { createClient } from '@/lib/supabase/server'const supabase = await createClient()
Route Handlerimport { createClient } from '@/lib/supabase/server'const supabase = await createClient()
Middlewareimport { 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() not getSession() 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 ComponentUse Server Component or useEffect
Wrong Supabase clientCheck table above
Passing full user objectPass only needed fields
any typesUse specific types from lib/supabase/types.ts
No error handlingAlways check for errors
Missing loading statesShow spinner/skeleton

See: 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:

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 }
}

Source

git clone https://github.com/Hildegaardchiasmal966/claude-skills/blob/master/nextjs-supabase-patterns/SKILL.mdView on GitHub

Overview

An actionable guide to implementing Next.js 15 App Router with Supabase SSR. It helps you decide between server and client components, choose the correct Supabase client, and apply security patterns for pages, components, and API routes.

How This Skill Works

Start with a quick decision tree to select the appropriate component type based on interactivity and data needs. Then implement using one of three patterns: Server Component with Data Fetching, Client Component with Interactivity, or Server Component → Client Component data flow, all using the correct Supabase client and security practices.

When to Use It

  • Building a server-rendered page that fetches data from Supabase
  • Adding interactive UI elements like Save buttons or forms
  • Passing server-fetched data to client components safely
  • Implementing API routes that read/write to Supabase
  • Working on real-time features like chat or live dashboards

Quick Start

  1. Step 1: Use the quick decision tree to choose between Server or Client Component
  2. Step 2: Pick the correct Supabase client (server, client, or Server Action)
  3. Step 3: Implement with Pattern 1 (server data fetch), Pattern 2 (client interactivity), or Pattern 3 (server→client data flow)

Best Practices

  • Prefer Server Components for data access and SSR where possible
  • Use the server Supabase client for server components and the client client for interactive UI
  • Pass only the necessary fields to Client Components; never full objects
  • Handle server errors gracefully and surface clear UI feedback on the client
  • Use Server Actions for form submissions to keep logic on the server

Example Use Cases

  • Server-side page: app/recipes/page.tsx fetching saved_recipes with @/lib/supabase/server
  • Save button: client component using @/lib/supabase/client
  • Form with validation: client component with interactive features
  • Profile page: server component retrieves user then renders a client editor
  • API route handler: server route using @/lib/supabase/server

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers