The stack most vibe-coded SaaS apps end up on, with the guard rails agents usually forget. Your agent learns where server code ends and client code begins, how to use Supabase auth with cookies, and that every table needs row level security before it holds real data.
What's in it
- Clear server/client boundary rules for the App Router
- Migrations-only schema changes, with RLS enabled on every new table
- A skill that audits every table's RLS policies and reports gaps in plain English
- Read-only Supabase MCP config so the agent can inspect, not drop, your database
You'll need
- Tools:
node,supabase-cli - Environment variables:
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY - MCP servers: supabase
Verification
| Check | Result | What we found |
|---|---|---|
| Listinglisting.yaml matches the schema and declares every file in the folder. | Pass | Schema valid; 6 files declared, none missing or extra
|
| Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. | Pass | All 6 files parse; front matter valid |
| AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. | Pass | CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
|
| Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. | Pass | Correct paths for all 5 agents; layouts re-discovered
|
| MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. | Pass | 1 server valid: supabase
|
| Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode or prompt-injection wording, and the listing's network flag is honest. | Pass | Clean: no credentials, risky shell or hidden text
|
| ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. | Pass | 2 references between files resolve for every agent |
| Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. | Pass | 6 files, 9.2 KB total; largest AGENTS.md 3.3 KB
|
| LicenceA recognised SPDX licence, with the licence text available. | Pass | MIT, by Agent Loadout; text in the repository LICENSE |
| Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. | Pass | Next.js App Router + @supabase/ssr app in the loadout's layout builds, and the loadout's SQL migration and RLS audit queries run on real Postgres with users isolated
|
What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results ("tested with agent") are separate and stay untested until an agent actually runs.
Install
Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.
Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md curl -fsSL --create-dirs -o CLAUDE.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/CLAUDE.md curl -fsSL --create-dirs -o .claude/skills/supabase-rls-audit/SKILL.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md curl -fsSL --create-dirs -o .mcp.json https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/mcp.json
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
CLAUDE.md | CLAUDE.md |
skills/supabase-rls-audit/SKILL.md | .claude/skills/supabase-rls-audit/SKILL.md |
mcp.json | .mcp.json |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.
Codex CLI reads AGENTS.md and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .agents/skills/supabase-rls-audit/SKILL.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
skills/supabase-rls-audit/SKILL.md | .agents/skills/supabase-rls-audit/SKILL.md |
Manual step mcp.json: Codex reads MCP servers from ~/.codex/config.toml (TOML, not JSON). Add each server with `codex mcp add` or copy the URL into an [mcp_servers.<name>] table. View the file.
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.
Cursor reads AGENTS.md + .cursor/rules/*.mdc and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .cursor/rules/supabase-migrations.mdc https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/.cursor/rules/supabase-migrations.mdc curl -fsSL --create-dirs -o .agents/skills/supabase-rls-audit/SKILL.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md curl -fsSL --create-dirs -o .cursor/mcp.json https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/mcp.json
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
.cursor/rules/supabase-migrations.mdc | .cursor/rules/supabase-migrations.mdc |
skills/supabase-rls-audit/SKILL.md | .agents/skills/supabase-rls-audit/SKILL.md |
mcp.json | .cursor/mcp.json |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.
Gemini CLI reads GEMINI.md (imports AGENTS.md with @./AGENTS.md) and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md curl -fsSL --create-dirs -o GEMINI.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/GEMINI.md curl -fsSL --create-dirs -o .agents/skills/supabase-rls-audit/SKILL.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
GEMINI.md | GEMINI.md |
skills/supabase-rls-audit/SKILL.md | .agents/skills/supabase-rls-audit/SKILL.md |
Manual step mcp.json: Gemini CLI reads MCP servers from the mcpServers block of .gemini/settings.json. Merge the servers in by hand, and use httpUrl instead of url for HTTP servers. View the file.
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.
GitHub Copilot reads AGENTS.md and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .agents/skills/supabase-rls-audit/SKILL.md https://loadout.jnw-it.com/raw/nextjs-supabase-saas/1.0.0/skills/supabase-rls-audit/SKILL.md
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
skills/supabase-rls-audit/SKILL.md | .agents/skills/supabase-rls-audit/SKILL.md |
Manual step mcp.json: In VS Code, MCP servers go in .vscode/mcp.json under a "servers" key (not "mcpServers"). Copilot CLI uses ~/.copilot/mcp-config.json. View the file.
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.
Files
Rules AGENTS.md Project rules for any agent that reads AGENTS.md. 55 lines
# AGENTS.md: Next.js + Supabase SaaS
You are working on a Next.js (App Router) SaaS backed by Supabase (Postgres, Auth, Storage).
Treat the database as production unless told otherwise.
## Stack and layout
- Next.js App Router in `app/`. TypeScript strict mode. No Pages Router code.
- Supabase clients live in `lib/supabase/`:
- `server.ts` creates a server client with `@supabase/ssr` and the Next.js `cookies()` store.
- `client.ts` creates a browser client. Only use it inside `"use client"` components.
- `middleware.ts` (or `proxy.ts` on newer Next.js) refreshes the auth session cookie on each request.
- Schema lives in `supabase/migrations/`. Generated types live in `lib/database.types.ts`.
## Server vs client
- Components are Server Components by default. Add `"use client"` only for state, effects or browser APIs.
- Fetch data in Server Components or Route Handlers, not in `useEffect`, unless it is genuinely live data.
- Mutations go through Server Actions or Route Handlers. Validate every input with `zod` on the server,
even if the form validated it already.
- Never import `lib/supabase/server.ts` or anything using secret env vars from a client component.
## Auth
- Get the user on the server with `supabase.auth.getUser()` (it verifies the JWT with Supabase).
Do not trust `getSession()` on the server for authorisation decisions.
- Protect pages by checking the user in the layout or page, and redirect if missing. Middleware alone is not enough.
- Authorisation belongs in RLS policies. App code checks are a convenience, not the security boundary.
## Keys and environment
- `NEXT_PUBLIC_SUPABASE_URL` and the publishable (anon) key are safe in the browser *only because RLS is on*.
- The secret / `service_role` key bypasses RLS. It must only be used in server-only code for admin jobs,
never prefixed `NEXT_PUBLIC_`, never logged, never committed.
- Keep `.env.local` out of git. Add any new variable to `.env.example` with a dummy value.
## Database changes
- Every schema change is a new migration: `supabase migration new <name>`. Never edit an applied migration.
- Every new table in an exposed schema gets `alter table ... enable row level security;` in the same migration,
plus explicit policies for each operation it needs (select, insert, update, delete).
- Prefer `(select auth.uid())` in policies over bare `auth.uid()` so Postgres can cache it per statement.
- Add indexes on columns used in policies (for example `user_id`, `org_id`).
- After a schema change, regenerate types: `supabase gen types typescript --local > lib/database.types.ts`.
## Payments (if present)
- Stripe webhooks are verified with the signing secret before any database write.
- Entitlements (plan, seats) are written by the webhook handler, never by the client.
## Before you say "done"
1. `npm run lint` and `npm run build` pass.
2. No secret key reachable from client bundles (`grep -r SERVICE_ROLE app components` is empty).
3. Any new table has RLS enabled and at least one policy. Run the `supabase-rls-audit` skill if unsure.
4. Summarise what changed, any migration added, and anything the human must do (env vars, dashboard settings).
## Do not
- Do not disable RLS to "make it work". Fix the policy instead.
- Do not run destructive SQL (`drop`, `truncate`, `delete` without `where`) against a remote project.
- Do not add a new auth library. Supabase Auth is the auth system. Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 11 lines
# CLAUDE.md
@AGENTS.md
## Claude Code notes
- Skills from this loadout live in `.claude/skills/`: `supabase-rls-audit`. Use them when their description matches the task.
- MCP servers for this project are in `.mcp.json`. If a server is not connected, tell the human rather than working around it.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.
<!-- From the Agent Loadout "Next.js + Supabase SaaS" v1.0.0 — https://loadout.jnw-it.com/loadouts/nextjs-supabase-saas/ --> Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 10 lines
# GEMINI.md
@./AGENTS.md
## Gemini CLI notes
- Skills from this loadout live in `.agents/skills/`: `supabase-rls-audit`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.
<!-- From the Agent Loadout "Next.js + Supabase SaaS" v1.0.0 — https://loadout.jnw-it.com/loadouts/nextjs-supabase-saas/ --> Cursor rule .cursor/rules/supabase-migrations.mdc Scoped Cursor rule that applies when editing SQL migrations. 43 lines
---
description: Rules for writing Supabase SQL migrations safely
globs: supabase/migrations/**/*.sql
alwaysApply: false
---
# Supabase migrations
- One concern per migration file. Name it for what it does: `add_projects_table`, not `update`.
- Never edit a migration that has been applied anywhere. Write a new one.
- Every `create table` in `public` (or any exposed schema) is followed in the same file by:
- `alter table <name> enable row level security;`
- explicit `create policy` statements for each operation the app needs.
- Write policies with a `to` role (`authenticated` or `anon`) and both `using` and, for writes, `with check`.
- Use `(select auth.uid())` inside policies, not bare `auth.uid()`.
- Index foreign keys and every column referenced by a policy.
- `security definer` functions must set `search_path = ''` and fully qualify table names.
- Destructive changes (`drop column`, `drop table`, type changes) need a comment explaining the data impact,
and should be split into expand → migrate data → contract steps when the table has real data.
Example:
```sql
create table public.projects (
id uuid primary key default gen_random_uuid(),
owner_id uuid not null references auth.users (id) on delete cascade,
name text not null check (char_length(name) between 1 and 120),
created_at timestamptz not null default now()
);
alter table public.projects enable row level security;
create index projects_owner_id_idx on public.projects (owner_id);
create policy "Owners can read their projects"
on public.projects for select to authenticated
using ((select auth.uid()) = owner_id);
create policy "Owners can create projects"
on public.projects for insert to authenticated
with check ((select auth.uid()) = owner_id);
``` Skill skills/supabase-rls-audit/SKILL.md Audits row level security across every table and reports gaps. 72 lines
---
name: supabase-rls-audit
description: Audit Supabase row level security. Use when asked to check database security, before launch, after adding tables, or when someone worries that users can see each other's data.
---
# Supabase RLS audit
Goal: find every way a signed-in or anonymous user could read or change rows they should not,
and report it in plain English with a fix for each.
## 1. Collect the facts
Use whichever source is available, in this order:
1. The Supabase MCP server (read-only) — run the SQL below with its query tool.
2. A local database: `supabase db dump --local --schema public` or `psql` against the local URL.
3. The migration files in `supabase/migrations/` (read them in order; later files win).
```sql
-- Tables in exposed schemas and whether RLS is on
select n.nspname as schema, c.relname as table, c.relrowsecurity as rls_enabled, c.relforcerowsecurity as rls_forced
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where c.relkind = 'r' and n.nspname in ('public')
order by 1, 2;
-- Every policy
select schemaname, tablename, policyname, cmd, roles, qual as using_expr, with_check
from pg_policies where schemaname in ('public')
order by tablename, cmd;
-- Views (they bypass RLS unless created with security_invoker)
select table_schema, table_name from information_schema.views where table_schema = 'public';
-- Security definer functions callable through the API
select p.proname, p.prosecdef, p.proconfig
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public' and p.prosecdef;
```
## 2. Check each table against these rules
Flag as **Critical**:
- RLS disabled on any table in `public`.
- A policy for `anon` or `public` roles that is `using (true)` on a table holding user data.
- An `update` policy without `with check`, letting users move rows to another owner.
- A view in `public` without `security_invoker = true` that selects from a protected table.
Flag as **High**:
- RLS enabled but no policies (the app is probably using the secret key to get round it — find out where).
- Policies that trust a column the user can write (for example `role` or `is_admin` on their own profile row).
- `security definer` functions without `set search_path = ''`.
- Storage buckets that are public but hold private uploads.
Flag as **Medium**:
- Bare `auth.uid()` instead of `(select auth.uid())` (performance, not security).
- Policy columns without indexes.
## 3. Check the app code
- Search for the secret / service role key: `grep -rn "SERVICE_ROLE\|sb_secret_" --include=*.ts --include=*.tsx .`
Any hit outside server-only files is **Critical**.
- Check that no `NEXT_PUBLIC_` variable holds a secret.
## 4. Report
Write a short report:
```
RLS audit — <date>
Critical: <n> High: <n> Medium: <n>
[Critical] public.invoices — RLS is disabled. Anyone with the publishable key can read every invoice.
Fix: new migration with `alter table public.invoices enable row level security;` and an owner-only select policy.
```
Then offer to write the fix as a **new migration**. Do not apply changes to a remote database yourself. All files: source zip · Licence MIT · By Agent Loadout · First listed
Installing and testing
How do I install Next.js + Supabase SaaS in Claude Code?
From your project root, put AGENTS.md, CLAUDE.md, skills/supabase-rls-audit/SKILL.md at .claude/skills/supabase-rls-audit/SKILL.md, mcp.json at .mcp.json. The "Claude Code" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/nextjs-supabase-saas/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Next.js + Supabase SaaS in Codex CLI?
From your project root, put AGENTS.md, skills/supabase-rls-audit/SKILL.md at .agents/skills/supabase-rls-audit/SKILL.md. The "Codex CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/nextjs-supabase-saas/codex.zip, which is already laid out. mcp.json needs a manual step: Codex reads MCP servers from ~/.codex/config.toml (TOML, not JSON). Add each server with `codex mcp add` or copy the URL into an [mcp_servers.<name>] table. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Next.js + Supabase SaaS in Cursor?
From your project root, put AGENTS.md, .cursor/rules/supabase-migrations.mdc, skills/supabase-rls-audit/SKILL.md at .agents/skills/supabase-rls-audit/SKILL.md, mcp.json at .cursor/mcp.json. The "Cursor" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/nextjs-supabase-saas/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Next.js + Supabase SaaS in Gemini CLI?
From your project root, put AGENTS.md, GEMINI.md, skills/supabase-rls-audit/SKILL.md at .agents/skills/supabase-rls-audit/SKILL.md. The "Gemini CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/nextjs-supabase-saas/gemini-cli.zip, which is already laid out. mcp.json needs a manual step: Gemini CLI reads MCP servers from the mcpServers block of .gemini/settings.json. Merge the servers in by hand, and use httpUrl instead of url for HTTP servers. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Next.js + Supabase SaaS in GitHub Copilot?
From your project root, put AGENTS.md, skills/supabase-rls-audit/SKILL.md at .agents/skills/supabase-rls-audit/SKILL.md. The "GitHub Copilot" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/nextjs-supabase-saas/copilot.zip, which is already laid out. mcp.json needs a manual step: In VS Code, MCP servers go in .vscode/mcp.json under a "servers" key (not "mcpServers"). Copilot CLI uses ~/.copilot/mcp-config.json. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
Has Next.js + Supabase SaaS been tested?
It passed the Level 1 automated install checks on 2026-10-16: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.
More for Claude Code, Codex, Cursor, Gemini CLI and GitHub Copilot.