AgentSkillsCN

supabase-patterns

当用户提出“设置 Supabase”“添加认证”“创建 Supabase 客户端”“使用 Supabase 认证”“配置 RLS”“添加行级安全”“设置 Google OAuth”,或在 Next.js 项目中使用 Supabase 时,可使用此技能。

SKILL.md
--- frontmatter
name: supabase-patterns
description: "This skill should be used when the user asks to \"setup Supabase\", \"add authentication\", \"create a Supabase client\", \"use Supabase auth\", \"configure RLS\", \"add row level security\", \"setup Google OAuth\", or is working with Supabase in a Next.js project."
version: 1.0.0

Supabase Integration Patterns for Next.js

Overview

Standards for integrating Supabase (auth, database, storage, realtime) with Next.js 15 App Router. Covers client setup, authentication flows, RLS policies, and data access patterns.

Client Setup

Server Client (for Server Components, API Routes, Server Actions)

File: src/lib/supabase/server.ts

typescript
import { createServerClient } from "@supabase/ssr"
import { cookies } from "next/headers"

export async function createClient() {
  const cookieStore = await cookies()

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll()
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options)
            )
          } catch {
            // Server Component'te cookie set edilemez, ignore
          }
        },
      },
    }
  )
}

Browser Client (for Client Components)

File: src/lib/supabase/client.ts

typescript
import { createBrowserClient } from "@supabase/ssr"

export function createClient() {
  return createBrowserClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
  )
}

Middleware (for Auth Session Refresh)

File: src/middleware.ts

typescript
import { createServerClient } from "@supabase/ssr"
import { NextResponse, type NextRequest } from "next/server"

export async function middleware(request: NextRequest) {
  let supabaseResponse = NextResponse.next({ request })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return request.cookies.getAll()
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) => {
            request.cookies.set(name, value)
            supabaseResponse.cookies.set(name, value, options)
          })
        },
      },
    }
  )

  const { data: { user } } = await supabase.auth.getUser()

  // Protected routes
  if (!user && request.nextUrl.pathname.startsWith("/dashboard")) {
    const url = request.nextUrl.clone()
    url.pathname = "/login"
    return NextResponse.redirect(url)
  }

  return supabaseResponse
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico|api/).*)"],
}

Authentication Patterns

Email/Password Login

typescript
"use server"

import { createClient } from "@/lib/supabase/server"
import { redirect } from "next/navigation"

export async function login(formData: FormData) {
  const supabase = await createClient()

  const { error } = await supabase.auth.signInWithPassword({
    email: formData.get("email") as string,
    password: formData.get("password") as string,
  })

  if (error) return { error: error.message }
  redirect("/dashboard")
}

OAuth (Google, GitHub)

typescript
"use server"

import { createClient } from "@/lib/supabase/server"
import { redirect } from "next/navigation"
import { headers } from "next/headers"

export async function loginWithGoogle() {
  const supabase = await createClient()
  const origin = (await headers()).get("origin")

  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: "google",
    options: {
      redirectTo: `${origin}/auth/callback`,
    },
  })

  if (data.url) redirect(data.url)
}

Auth Callback Route

File: src/app/auth/callback/route.ts

typescript
import { createClient } from "@/lib/supabase/server"
import { NextResponse } from "next/server"

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url)
  const code = searchParams.get("code")

  if (code) {
    const supabase = await createClient()
    await supabase.auth.exchangeCodeForSession(code)
  }

  return NextResponse.redirect(`${origin}/dashboard`)
}

Data Access Patterns

Server Component (Direct Query)

typescript
import { createClient } from "@/lib/supabase/server"

export default async function PostsPage() {
  const supabase = await createClient()
  const { data: posts } = await supabase
    .from("posts")
    .select("*, author:users(name, avatar_url)")
    .order("created_at", { ascending: false })

  return <PostList posts={posts ?? []} />
}

Client Component (React Query + Supabase)

typescript
"use client"

import { useQuery } from "@tanstack/react-query"
import { createClient } from "@/lib/supabase/client"

export const usePosts = () => {
  const supabase = createClient()

  return useQuery({
    queryKey: ["posts"],
    queryFn: async () => {
      const { data, error } = await supabase
        .from("posts")
        .select("*")
        .order("created_at", { ascending: false })

      if (error) throw error
      return data
    },
  })
}

Row Level Security (RLS) Patterns

Always enable RLS on all tables. Common policies:

sql
-- Users can read all public posts
CREATE POLICY "Public posts readable by all"
ON posts FOR SELECT
USING (published = true);

-- Users can only edit their own posts
CREATE POLICY "Users can update own posts"
ON posts FOR UPDATE
USING (auth.uid() = user_id);

-- Users can only insert as themselves
CREATE POLICY "Users can insert own posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = user_id);

-- Users can only delete their own posts
CREATE POLICY "Users can delete own posts"
ON posts FOR DELETE
USING (auth.uid() = user_id);

Environment Variables

Required in .env.local:

code
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...

Anti-Patterns

  • Never use service_role key on the client side
  • Never skip RLS - enable it on every table
  • Never store Supabase URL/key in code, use env variables
  • Never use supabase.auth.getSession() on the server - use getUser() instead (security)
  • Never create Supabase client outside of functions (breaks cookie handling)