nextjs15-supabase-cursorrules — Cursor rule: 27 architecture bans
A Cursor rule that keeps the agent inside a Next.js 15, Supabase and TypeScript strict stack — from session checks to RLS and Stripe keys.
.cursor/rules/nextjs15-supabase-cursorrules-prompt-file.mdcWhat nextjs15-supabase-cursorrules-prompt-file does
The file lays out 27 rules for a project on Next.js 15 (App Router) with React 19, Supabase, Tailwind CSS and shadcn/ui. The agent reads them before writing code and is told to follow every rule unconditionally.
Among the bans: calling getSession() on the server instead of getUser(), touching params and searchParams synchronously, importing from the deprecated @supabase/auth-helpers-nextjs, tables without RLS, Stripe keys starting with sk_ in NEXT_PUBLIC_ variables, the any type, and auth enforcement inside middleware. Mutations must go through Server Actions with Zod validation and an ActionResponse<T> return, and every data-fetching page needs sibling loading.tsx and error.tsx files.
Code samples are included — a correct and a wrong version for each of the ten rules spelled out in the file. The full set of 27 lives separately in the vibestackdev/vibe-stack repository and installs with npx vibe-stack-rules init. If your project is not on Supabase, or you have already moved auth elsewhere, some of these bans will fight your existing code — worth checking which rules actually apply.
What it helps with
- catch a server-side
getSession()call before it ships - remember to
awaitparamsandsearchParamsin Next.js 15 - turn on RLS for every Supabase table
- drop
anyand keep TypeScript strict - keep an
sk_Stripe key out ofNEXT_PUBLIC_
Example prompts for the agent
- Add a product page: read
idfromparamswithawaitand fetch the row from Supabase. - Write a Server Action that creates a record with Zod validation and a
getUser()check. - Review my code for violations of the Next.js 15 and Supabase rules in this file.
How to install nextjs15-supabase-cursorrules-prompt-file
Manually: put the files in place
.cursor/rules/nextjs15-supabase-cursorrules-prompt-file.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/nextjs15-supabase-cursorrules-prompt-file.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/nextjs15-supabase-cursorrules-prompt-file.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/nextjs15-supabase-cursorrules-prompt-file.mdManually: put the files in place
.clinerules/nextjs15-supabase-cursorrules-prompt-file.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about nextjs15-supabase-cursorrules-prompt-file
How do I install nextjs15-supabase-cursorrules in Cursor?
Place the file at .cursor/rules/nextjs15-supabase-cursorrules-prompt-file.mdc in your project root. Cursor picks it up as a rule.
What does this rule do?
It gives the agent 27 architecture constraints for a Next.js 15, Supabase and TypeScript strict stack: session verification, async params, RLS, Server Actions, Stripe keys.
Where is the full set of 27 rules?
The file points to the vibestackdev/vibe-stack repository and the npx vibe-stack-rules init command for a quick install.
Source text
nextjs15-supabase-cursorrules-prompt-file.mdc · CC0-1.0
---
description: "27 architecture rules preventing AI hallucinations: insecure auth (getSession vs getUser), synchronous params, deprecated imports, missing RLS, and Stripe key exposure. Built for Cursor Agent and Claude Code."
globs: **/*
alwaysApply: false
---
# Next.js 15 + Supabase Architecture Rules
You are an expert Next.js 15 developer working with Supabase, TypeScript (strict), and shadcn/ui.
Follow ALL rules below unconditionally. If you are tempted to deviate, re-read the rule.
## Tech Stack
- Framework: Next.js 15 (App Router) with React 19
- Language: TypeScript (strict mode)
- Styling: Tailwind CSS + shadcn/ui
- Database: Supabase (PostgreSQL + RLS)
- Auth: Supabase SSR (cookie-based, @supabase/ssr)
- Validation: Zod
- Payments: Stripe (server-side only)
## RULE 1: NEVER use getSession() on the server
SECURITY CRITICAL. getSession() reads the JWT from cookies WITHOUT verifying it.
A forged cookie passes silently. ALWAYS use getUser() for server-side auth.
```typescript
// ✅ CORRECT — verified with Supabase auth server
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) redirect('/login')
// ❌ WRONG — reads JWT without verification, session can be forged
const { data: { session } } = await supabase.auth.getSession()
```
## RULE 2: NEVER access params synchronously in Next.js 15
In Next.js 15, params and searchParams are Promises. Synchronous access compiles
but crashes at runtime.
```typescript
// ✅ CORRECT
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
}
// ❌ WRONG — runtime crash
export default function Page({ params }: { params: { id: string } }) {
const { id } = params // TypeError at runtime
}
```
## RULE 3: NEVER import from @supabase/auth-helpers-nextjs
This package is deprecated. It does NOT work with Next.js 15 App Router cookies.
ALWAYS use @supabase/ssr with manual cookie handling.
```typescript
// ✅ CORRECT
import { createServerClient } from '@supabase/ssr'
// ❌ WRONG — deprecated, broken with App Router
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
```
## RULE 4: All database tables MUST have RLS enabled
Every Supabase table must have Row Level Security enabled. Without RLS, any
user with the anon key can read ALL data from the table.
```sql
-- ✅ Always add after CREATE TABLE:
ALTER TABLE public.my_table ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can only read their own data"
ON public.my_table FOR SELECT
USING (auth.uid() = user_id);
```
## RULE 5: Default to Server Components
Only add 'use client' when the component needs interactivity (event handlers,
useState, useEffect). Push 'use client' to the smallest leaf component possible.
## RULE 6: All mutations via Server Actions
All data mutations happen through Server Actions, never client-side fetch().
Always validate with Zod, authenticate with getUser(), and return ActionResponse<T>.
```typescript
'use server'
import { z } from 'zod'
type ActionResponse<T = void> =
| { success: true; data: T }
| { success: false; error: string }
const Schema = z.object({ title: z.string().min(1).max(200) })
export async function createItem(input: unknown): Promise<ActionResponse> {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return { success: false, error: 'Unauthorized' }
const result = Schema.safeParse(input)
if (!result.success) return { success: false, error: 'Invalid input' }
const { error } = await supabase.from('items').insert({ ...result.data, user_id: user.id })
if (error) return { success: false, error: 'Failed to create item' }
revalidatePath('/items')
return { success: true, data: undefined }
}
```
## RULE 7: Error boundaries for every data-fetching page
Every page that fetches data MUST have sibling loading.tsx and error.tsx files.
## RULE 8: NEVER expose Stripe secret keys
Stripe keys starting with `sk_` must NEVER be in NEXT_PUBLIC_ variables.
Use process.env.STRIPE_SECRET_KEY (server-only).
## RULE 9: TypeScript strict mode, no `any`
NEVER use `any`. Use `unknown` with Zod validation or type narrowing.
tsconfig.json MUST have `strict: true`.
## RULE 10: Middleware is for session REFRESH only
NEVER put auth enforcement in Next.js middleware. Middleware runs on Edge Runtime
and cannot verify Supabase JWTs. Auth enforcement belongs in layouts/pages with getUser().
---
Full rule set (27 rules) available at: https://github.com/vibestackdev/vibe-stack
Quick install: npx vibe-stack-rules init
© PatrickJS · CC0-1.0 · PatrickJS/awesome-cursorrules