--- name: nextjs-supabase-auth description: Expert integration of Supabase Auth with Next.js App Router risk: none source: vibeship-spawner-skills (Apache 2.0) date_added: 2026-02-27 --- # Next.js + Supabase Auth Expert integration of Supabase Auth with Next.js App Router ## Capabilities - nextjs-auth - supabase-auth-nextjs - auth-middleware - auth-callback ## Prerequisites - Required skills: nextjs-app-router, supabase-backend ## Patterns ### Supabase Client Setup Create properly configured Supabase clients for different contexts **When to use**: Setting up auth in a Next.js project // lib/supabase/client.ts (Browser client) 'use client' import { createBrowserClient } from '@supabase/ssr' export function createClient() { return createBrowserClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ) } // lib/supabase/server.ts (Server client) 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) { cookiesToSet.forEach(({ name, value, options }) => { cookieStore.set(name, value, options) }) }, }, } ) } ### Auth Middleware Protect routes and refresh sessions in middleware **When to use**: You need route protection or session refresh // middleware.ts import { createServerClient } from '@supabase/ssr' import { NextResponse, type NextRequest } from 'next/server' export async function middleware(request: NextRequest) { let response = 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 }) => { response.cookies.set(name, value, options) }) }, }, } ) // Refresh session if expired const { data: { user } } = await supabase.auth.getUser() // Protect dashboard routes if (request.nextUrl.pathname.startsWith('/dashboard') && !user) { return NextResponse.redirect(new URL('/login', request.url)) } return response } export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'], } ### Auth Callback Route Handle OAuth callback and exchange code for session **When to use**: Using OAuth providers (Google, GitHub, etc.) // app/auth/callback/route.ts 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') const next = searchParams.get('next') ?? '/' if (code) { const supabase = await createClient() const { error } = await supabase.auth.exchangeCodeForSession(code) if (!error) { return NextResponse.redirect(`${origin}${next}`) } } return NextResponse.redirect(`${origin}/auth/error`) } ### Server Action Auth Handle auth operations in Server Actions **When to use**: Login, logout, or signup from Server Components // app/actions/auth.ts 'use server' import { createClient } from '@/lib/supabase/server' import { redirect } from 'next/navigation' import { revalidatePath } from 'next/cache' export async function signIn(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 } } revalidatePath('/', 'layout') redirect('/dashboard') } export async function signOut() { const supabase = await createClient() await supabase.auth.signOut() revalidatePath('/', 'layout') redirect('/') } ### Get User in Server Component Access the authenticated user in Server Components **When to use**: Rendering user-specific content server-side // app/dashboard/page.tsx import { createClient } from '@/lib/supabase/server' import { redirect } from 'next/navigation' export default async function DashboardPage() { const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() if (!user) { redirect('/login') } return (