mirror of
https://github.com/supabase/agent-skills.git
synced 2026-03-27 10:09:26 +08:00
* rebase and house keeping * fix supabase sdk reference files after docs review * update agents.md
5.6 KiB
5.6 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Next.js App Router Integration | HIGH | Enables proper SSR auth with session refresh and type-safe queries | nextjs, app-router, server-components, proxy, ssr |
Next.js App Router Integration
Complete setup for Next.js 13+ App Router with Supabase.
Incorrect:
// Using deprecated package and wrong cookie methods
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerComponentClient({ cookies: () => cookieStore })
}
Correct:
// Using @supabase/ssr with getAll/setAll
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(url, key, {
cookies: {
getAll() { return cookieStore.getAll() },
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// Server Component - ignore
}
},
},
})
}
1. Install Packages
npm install @supabase/supabase-js @supabase/ssr
2. Environment Variables
# .env.local
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
The publishable key (
sb_publishable_...) is replacing the legacyanonkey. Both work during the transition period.
3. Create Client Utilities
Browser Client (lib/supabase/client.ts):
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!
)
}
Server Client (lib/supabase/server.ts):
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_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
// Server Component - ignore
}
},
},
}
)
}
4. Proxy (Required)
// proxy.ts
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function proxy(request: NextRequest) {
let supabaseResponse = NextResponse.next({ request })
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({ request })
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// Refresh the Auth token
// getClaims() validates JWT locally (fast, no network request, requires asymmetric keys)
// getUser() validates via Auth server round-trip (detects logouts/revocations)
await supabase.auth.getUser()
return supabaseResponse
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
}
5. Usage Examples
Server Component:
import { createClient } from '@/lib/supabase/server'
export default async function Page() {
const supabase = await createClient()
const { data: posts } = await supabase.from('posts').select()
return <ul>{posts?.map(post => <li key={post.id}>{post.title}</li>)}</ul>
}
Client Component:
'use client'
import { createClient } from '@/lib/supabase/client'
import { useEffect, useState } from 'react'
export default function RealtimePosts() {
const [posts, setPosts] = useState<any[]>([])
useEffect(() => {
const supabase = createClient()
const channel = supabase
.channel('posts')
.on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts' },
(payload) => setPosts(prev => [...prev, payload.new])
)
.subscribe()
return () => { supabase.removeChannel(channel) }
}, [])
return <ul>{posts.map(post => <li key={post.id}>{post.title}</li>)}</ul>
}
Server Action:
'use server'
import { createClient } from '@/lib/supabase/server'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const supabase = await createClient()
await supabase.from('posts').insert({ title: formData.get('title') })
revalidatePath('/posts')
}
Common Pitfalls
| Pitfall | Solution |
|---|---|
Using auth-helpers-nextjs |
Use @supabase/ssr |
| Individual cookie methods | Use getAll()/setAll() |
Trusting getSession() |
Use getUser() (server-verified) or getClaims() (local JWT validation, requires asymmetric keys) |
| Missing proxy | Required for session refresh |
| Reusing server client | Create fresh client per request |