Skip to content
Loadout

Next.js + Supabase

Next.js Supabase Cursor rules

Next.js 16.4 and Supabase SSR, with the APIs that actually work.

Free packs are MIT licensed

Verified on 2026-10-15 · zip downloads · version-pinned

Next.jsReactSupabase

Verified for

  • Next.js 16.4.1
  • React 19.3
  • @supabase/ssr 0.12.7
  • @supabase/supabase-js current, installed alongside @supabase/ssr

Last tested 2026-10-15

Snippet-checked

doc-verified; TypeScript snippets pass tsc --strict (TypeScript 5, @types/react 19) with zod and the Supabase client stubbed

The rules

Stops the mistakes agents still make on this stack: sync params, a middleware.ts file Next.js 16 never runs, the deprecated auth-helpers package, and trusting getSession() for authorization. Rules match the Next.js 16.4 and Supabase SSR docs as of October 2026.

Related fixes

Who it's for

People vibe-coding a Next.js App Router app with Supabase Auth, Postgres, and RLS.

Loadout Pro Bundle

$1+ · pay what you want

What you get

  • Phaser 4 (4.2.1)
  • three.js (r186)
  • FastAPI (0.143 / Pydantic 2.14)

Each pack ships as AGENTS.md, CLAUDE.md, Cursor .mdc, and Copilot instructions, with 12 months of free updates.

Get Pro on Gumroad

Install

Copy one file, or download the zip for that tool. The zip already uses the path below.

  1. Cursor

    .cursor/rules/nextjs-supabase.mdc

    1. Download the Cursor zip, or copy the .mdc file from this page.
    2. Save it as .cursor/rules/nextjs-supabase.mdc.
    3. Commit it. Cursor reads project rules from that folder.
  2. Claude Code

    CLAUDE.md

    1. Download the Claude zip, or copy CLAUDE.md.
    2. Put CLAUDE.md in the project root.
    3. Claude Code reads it as project memory on the next session.
  3. AGENTS.md

    AGENTS.md

    1. Download the Agents zip, or copy AGENTS.md.
    2. Put AGENTS.md in the project root.
    3. Agents that read AGENTS.md, including Codex and Cursor, pick it up there.
  4. GitHub Copilot

    .github/copilot-instructions.md

    1. Download the Copilot zip, or copy the instructions file.
    2. Save it as .github/copilot-instructions.md.
    3. Copilot reads repository instructions from that path.

Questions

What does the Next.js + Supabase pack do?
Stops the mistakes agents still make on this stack: sync params, a middleware.ts file Next.js 16 never runs, the deprecated auth-helpers package, and trusting getSession() for authorization. Rules match the Next.js 16.4 and Supabase SSR docs as of October 2026.
Who is the Next.js + Supabase pack for?
People vibe-coding a Next.js App Router app with Supabase Auth, Postgres, and RLS.
Which versions does the Next.js + Supabase pack pin?
Next.js: 16.4.1 React: 19.3 @supabase/ssr: 0.12.7 @supabase/supabase-js: current, installed alongside @supabase/ssr Last tested 2026-10-15.
How do I install the Next.js + Supabase pack?
Copy .cursor/rules/nextjs-supabase.mdc into the repo for Cursor, CLAUDE.md for Claude Code, AGENTS.md for other agents, and .github/copilot-instructions.md for GitHub Copilot. A format zip already uses that path. Then commit the files.
When was the Next.js + Supabase pack last tested, and how?
Last tested 2026-10-15. doc-verified; TypeScript snippets pass tsc --strict (TypeScript 5, @types/react 19) with zod and the Supabase client stubbed.

Plain text

The same pack as markdown, plus each rules file on its own URL.

Files

.cursor/rules/nextjs-supabase.mdc

---
description: Next.js 16.4 App Router with @supabase/ssr 0.12.7. Async params, proxy.ts, getClaims, RLS.
globs: "{app,src,lib,components}/**/*.{ts,tsx},proxy.ts,middleware.ts,**/*.sql"
alwaysApply: false
---
# Next.js 16.4 + Supabase SSR

Pinned on 2026-10-15: Next.js 16.4.1 (App Router), React 19.3, `@supabase/ssr` 0.12.7, `@supabase/supabase-js`. Do not generate Pages Router, `middleware.ts`, or `@supabase/auth-helpers-nextjs`.

## Routing and components
- Server Components are the default. Add `"use client"` only when the file uses state, effects, browser APIs, or event handlers. Push that file to a leaf component.
- `params` and `searchParams` are Promises. Always `await` them. Synchronous access typechecks in places and crashes at runtime.
- `cookies()`, `headers()`, and `draftMode()` from `next/headers` are async. `await` them.
- On Next.js 16 the request interceptor is `proxy.ts` and the export is `proxy`. A file named `middleware.ts` is not called, so sessions never refresh.
- New `create-next-app` projects enable Cache Components. Do not cache a response that depends on a cookie or a user. Authenticated reads stay dynamic.

```tsx
export default async function Page({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  return <h1>{id}</h1>
}
```

## Supabase clients
- Install `@supabase/supabase-js` and `@supabase/ssr`. Never import `@supabase/auth-helpers-nextjs`.
- Env names from the current docs: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`. The secret key and the service-role key stay server-only, with no `NEXT_PUBLIC_` prefix.
- Browser code uses `createBrowserClient`. Server Components, Server Actions, and Route Handlers use `createServerClient` with `cookies.getAll` / `cookies.setAll`.
- `setAll` receives a second argument, the cache headers. Apply `Cache-Control`, `Expires`, and `Pragma` on the response. Skipping them lets a CDN store one user's session cookie and serve it to someone else.
- In a Server Component, `setAll` cannot write cookies. Catch and ignore that error. Refreshing happens in `proxy.ts`.

## Auth
- To decide whether a request is allowed, call `supabase.auth.getClaims()`. It verifies the JWT (locally against JWKS on asymmetric keys, which new projects use).
- Use `getUser()` only when you need the current user row from the Auth server. Use `getSession()` only to read the raw token, and never trust `session.user` for authorization.
- `proxy.ts` refreshes the session by calling `getClaims()`, copies cookies onto both the request and the response, and returns the response that `setAll` last built. Pages and actions still call `getClaims()` themselves. Proxy is not the only lock.
- Sign-in and sign-out go through Server Actions, not a client `fetch` to your own route.

## Database
- Every table in `public` has RLS enabled. Add policies only for the operations clients should be allowed to do, because any operation without a policy is denied. A table without RLS is readable and writable through the Data API under the default grants.
- Select named columns. Do not `.select("*")` and do not return the whole row to the client.
- Validate mutation input with Zod, then check `getClaims()`, then write. Return a typed `{ ok, error }` object, not a thrown string.

```ts
const { data, error } = await supabase
  .from("notes")
  .select("id, title, created_at")
  .eq("user_id", claims.sub)
```

## Server Actions and forms
- Put `"use server"` at the top of an actions file, or inside the function body. Do not export non-async values from a `"use server"` file.
- Every action re-checks auth with `getClaims()`. An action is a public HTTP endpoint even if no button calls it.
- After a write, call `revalidatePath` or `revalidateTag` for the page that shows the data. Do not force a full `router.refresh()` from the client to fix stale data.

```ts
"use server"

import { z } from "zod"
import { createClient } from "@/lib/supabase/server"

const NoteInput = z.object({ title: z.string().min(1).max(200) })

export async function createNote(formData: FormData) {
  const parsed = NoteInput.safeParse({ title: formData.get("title") })
  if (!parsed.success) return { ok: false, error: "Invalid title" }

  const supabase = await createClient()
  const { data, error } = await supabase.auth.getClaims()
  if (error || !data) return { ok: false, error: "Not signed in" }

  const { error: insertError } = await supabase
    .from("notes")
    .insert({ title: parsed.data.title, user_id: data.claims.sub })
  if (insertError) return { ok: false, error: insertError.message }
  return { ok: true }
}
```

## Deploying on Vercel
- Set `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` in the Vercel project for Preview and Production.
- Keep the Supabase secret key out of the client bundle. Search the build for `NEXT_PUBLIC_` names before shipping.
- Add every deployed URL (production and preview pattern) to Supabase Auth's redirect allow list, or OAuth and magic links fail.

## Done checklist
1. `npx tsc --noEmit` and `next build` pass, or you said why they could not run.
2. New tables have RLS enabled plus policies, in a migration file.
3. No `getSession()` is used for authorization, and no `select("*")` was added.

Changelog

Placeholder. This is the first published cut, last verified on 2026-10-15. Version notes will replace this block when the pack is revised.