To secure a Cursor + Next.js + Supabase stack, you need to: (1) use the correct Supabase client for each context (createBrowserClient for Client Components, createServerClient for Server Components/Actions), (2) enable Row Level Security on all Supabase tables, (3) always verify authentication with auth.getUser() in Server Actions before mutations, (4) never trust user ID from form data - always get it from the authenticated session, and (5) configure middleware to protect authenticated routes. This blueprint covers Next.js App Router client patterns and Server Action security.
TL;DR
Next.js 14+ with App Router adds complexity to Supabase integration. You need different Supabase clients for Server Components, Server Actions, Route Handlers, and the browser. Enable RLS on all tables, use the server client with service role for admin operations, and always verify authentication in Server Actions. Common AI-generated mistakes include mixing client types and missing auth checks in server code.
Platform Guides & Checklists
Cursor Security Guide
Next.js Security Guide
Supabase Security Guide
Pre-Launch Checklist
Understanding Next.js Supabase Clients
Next.js App Router requires different Supabase clients for different contexts:
| Context | Supabase Client | Use Case |
|---|---|---|
| Client Components | createBrowserClient | Browser-side data fetching |
| Server Components | createServerClient (cookies) | SSR data fetching |
| Server Actions | createServerClient (cookies) | Form submissions, mutations |
| Middleware | createMiddlewareClient | Auth refresh, redirects |
Part 1: Setting Up Supabase Clients Next.js Supabase
Browser Client Next.js
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
Server Client Next.js
import { createServerClient, type CookieOptions } 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 {
// Called from Server Component, ignore
}
},
},
}
)
}
Part 2: Authentication in Server Actions Next.js Cursor
Always Verify Authentication Next.js Supabase
AI-generated Server Actions often miss authentication checks:
// app/actions.ts - DANGEROUS
'use server'
export async function updateProfile(formData: FormData) {
const supabase = await createClient()
// Missing auth check!
await supabase.from('profiles').update({
name: formData.get('name')
}).eq('id', formData.get('userId'))
}
// app/actions.ts - SECURE
'use server'
import { createClient } from '@/lib/supabase/server'
export async function updateProfile(formData: FormData) {
const supabase = await createClient()
// Verify authentication
const { data: { user }, error: authError } = await supabase.auth.getUser()
if (authError || !user) {
throw new Error('Unauthorized')
}
// Only update current user's profile
const { error } = await supabase.from('profiles').update({
name: formData.get('name')
}).eq('id', user.id) // Use authenticated user's ID
if (error) {
throw new Error('Failed to update profile')
}
return { success: true }
}
Critical: Never trust user ID from form data. Always get the user ID from auth.getUser() to prevent user impersonation.
Security Checklist
Pre-Launch Checklist for Cursor + Next.js + Supabase
Correct Supabase client for each context
RLS enabled on all tables
Auth verification in all Server Actions
Middleware protects authenticated routes
Service role key only in server code
User ID from auth, not from client input
Environment variables in Vercel
.cursorignore excludes .env files
Alternative Stack Configurations
Cursor + Supabase + Vercel General Cursor + Supabase guide without Next.js-specific patterns. Framework-agnostic approach.
Next.js + Supabase + Vercel
Framework-focused guide without Cursor-specific considerations.
Cursor + Next.js + Prisma
Swap Supabase for Prisma ORM. Different security model with type-safe queries.
Why do I need different Supabase clients?
Next.js App Router runs code in different contexts (browser, server, edge). Each context handles cookies and authentication differently. Using the wrong client causes auth state mismatches or security vulnerabilities.
Can I use getSession() instead of getUser()?
For security-critical operations, use getUser() as it verifies the JWT with Supabase's servers. getSession() only checks the local JWT and could be spoofed. For read-only UI decisions, getSession() is acceptable.
Do I need middleware if I have RLS?
Yes. RLS protects your database, but middleware protects your UI. Without middleware, unauthenticated users can see protected pages (even if the data is empty due to RLS). Both layers are important.