STACK IT FAST
ALL RULES & SKILLS

Next.js 15 + Drizzle ORM + Supabase

Raw .MD nextjs-drizzle-supabase
CURATED RULE AGENTS.MD + CLAUDE.MD + .MDC + SKILL.MD

Production guidelines for Next.js App Router, Drizzle ORM with postgres-js, Supabase Transaction Pooler (prepare: false), and RLS security.

AGENTS.md
Paste in your project root
SKILL.md
Installs to .claude/skills/nextjs-drizzle-supabase
INTERACTIVE RULE & SKILL VIEWER (AGENTS.MD, CLAUDE.MD, .MDC & SKILL.MD)
Optimized for:CursorClaude CodeWindsurfAGY
AGENTS.md·58 lines · 3.6 KB
1# Project Architecture & Guidelines (Next.js + Drizzle + Supabase)
2
3## 1. System Architecture
4- **Framework**: Next.js 15 (App Router, Server Components by default).
5- **Database & ORM**: PostgreSQL via Supabase, typed schema managed with Drizzle ORM (`drizzle-orm`, `drizzle-kit`, `postgres-js`).
6- **Auth & Storage**: Supabase Auth (`@supabase/ssr` cookie handler) and Supabase Storage.
7- **Connection Pooling**: Supabase Transaction Pooler (port 6543) via `postgres-js`.
8
9## 2. Database Connection Rules (Critical Supabase Pooler Gotcha)
10- When connecting to Supabase's Transaction Pooler (port 6543), prepared statements MUST be disabled (`prepare: false`). Otherwise queries will fail with `prepared statement "drizzle_s_1" already exists`.
11- Always implement the singleton connection pattern in `src/db/index.ts`:
12 ```typescript
13 import { drizzle } from 'drizzle-orm/postgres-js';
14 import postgres from 'postgres';
15 import * as schema from './schema';
16
17 const connectionString = process.env.DATABASE_URL!;
18 const globalForDb = globalThis as unknown as { conn: postgres.Sql | undefined };
19
20 const client =
21 globalForDb.conn ??
22 postgres(connectionString, {
23 prepare: false, // REQUIRED for Supabase Transaction Pooler (:6543)
24 max: process.env.NODE_ENV === 'production' ? 10 : 1,
25 });
26
27 if (process.env.NODE_ENV !== 'production') globalForDb.conn = client;
28
29 export const db = drizzle(client, { schema });
30 ```
31- For Drizzle Kit migrations, provide the direct PostgreSQL connection URL (`DIRECT_URL` on port 5432) in `drizzle.config.ts`.
32
33## 3. Schema Organization & Relational Queries
34- Group modular tables in `src/db/schema/` (e.g., `users.ts`, `posts.ts`, `auth.ts`).
35- Define explicit relations in `src/db/schema/relations.ts` using `relations()` from `drizzle-orm`.
36- Use Drizzle Relational Queries (`db.query.<table_name>.findFirst({ with: { ... } })`) for typed nested fetches without manual joins.
37
38## 4. Server Actions & Mutations
39- Place server actions in `src/actions/` with `'use server'` at the top.
40- Validate all inputs using `zod` schemas before executing database queries.
41- Authenticate the calling session using Supabase SSR client before executing privileged operations.
42
43## 5. Common Pitfalls to Avoid
44- ❌ Forgetting `prepare: false` on Supabase pooled connections.
45- ❌ Running `drizzle-kit push` against production databases; always use generated SQL migrations (`drizzle-kit generate` & `drizzle-kit migrate`).
46- ❌ Fetching data in client component `useEffect`; fetch in React Server Components or Server Actions.
47
48## 6. Testing Conventions
49- Use Vitest for unit tests on Server Actions and Drizzle query helpers; mock the `db` singleton with a test-only in-memory or containerized Postgres.
50- Use Playwright for e2e coverage of auth flows (Supabase SSR cookie handling is easy to break silently across route handler changes).
51- Test RLS policies directly with SQL assertions against a Supabase local dev instance — application-level tests alone won't catch a missing policy.
52- Run `tsc --noEmit` as a required check; Drizzle's inferred types catch most schema/query mismatches before runtime.
53
54## 7. Git Workflow & PR Conventions
55- Conventional Commits (`feat:`, `fix:`, `refactor:`) scoped to the route or table, e.g. `fix(db): add missing index on posts.author_id`.
56- Generated Drizzle migrations (`drizzle-kit generate`) ship in the same PR as the schema change; never hand-edit generated SQL.
57- Require `tsc --noEmit` and `bun run build` green before merge — a broken Server Action fails silently in dev but hard in production.
58- Squash-merge; never `drizzle-kit push` directly against a shared or production database from a feature branch.
ARCHITECTURE NOTES & IMPLEMENTATION GUIDE
Export as Markdown

Architecture Overview

Standardized production guidelines for Next.js 15 App Router, Drizzle ORM, and Supabase PostgreSQL.

Verified Real-World Adoption

Utilized by modern high-velocity platforms like Supabase Studio, Dub.co, and CopilotKit.

Key Architectural Nuances

  • Prepared Statement Disabling: Supabase’s transaction pooler requires prepare: false to prevent statement naming collisions across ephemeral Lambda invocations.
  • Relational Query Builder: Combines SQL performance with declarative, type-safe nested object queries.
FREQUENTLY ASKED QUESTIONS

Does this AGENTS.md work with Cursor, Claude Code, and Windsurf?

Yes — AGENTS.md is the open, cross-tool standard read by Cursor, Claude Code, Windsurf, and 30+ other agents. A dedicated .mdc file is also included for Cursor's native .cursor/rules format.

Why does Supabase's transaction pooler require prepare: false?

PgBouncer's transaction pooling mode reassigns the underlying Postgres connection after every transaction, but prepared statements are tied to a specific connection. When postgres-js tries to reuse a prepared statement name on a connection that doesn't have it, Postgres throws a 'prepared statement already exists' or 'does not exist' error — disabling prepared statements avoids the mismatch entirely.

Why use a globalThis singleton for the Drizzle client instead of creating it per request?

Next.js hot module reloading in development re-executes module-level code on every file save, which would otherwise open a fresh Postgres connection each time and quickly exhaust the connection pool. Caching the client on globalThis in development ensures the same connection is reused across HMR reloads.

MORE AI AGENT CODING RULES & SKILLS
View All Rules & Skills