Cursor + Next.js + Supabase Security Blueprint

Share

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.

Setup Time2-3 hours

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:

ContextSupabase ClientUse Case
Client ComponentscreateBrowserClientBrowser-side data fetching
Server ComponentscreateServerClient (cookies)SSR data fetching
Server ActionscreateServerClient (cookies)Form submissions, mutations
MiddlewarecreateMiddlewareClientAuth refresh, redirects

Part 1: Setting Up Supabase Clients Next.js Supabase

Browser Client Next.js

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_ANON_KEY!
  )
}

Server Client Next.js

lib/supabase/server.ts
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:

INSECURE: Missing auth check
// 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'))
}
SECURE: With auth verification
// 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.

Building with Next.js and Supabase?

Scan your app for authentication and RLS issues.

Start Free Scan
Security Blueprints

Cursor + Next.js + Supabase Security Blueprint