Next.js has two routing systems that look similar in code but behave completely differently, and most model training data blends them. An agent needs to know which router the project uses, which Next major version is installed, and where the server/client boundary sits, because almost every wrong answer in this ecosystem comes from mixing App Router and Pages Router idioms. It also needs to know that server components, route handlers, and server actions all run on the server but have different rules about caching, streaming, and input validation.
Save this as AGENTS.md in your project root, and symlink CLAUDE.md to it so Claude Code reads the same rules. Adapt the commands to match your repository before committing it.
# AGENTS.md
This is a subscription SaaS built with Next.js (App Router), Supabase, and Stripe.
## Project
Stack: Next.js 15 (App Router), TypeScript (strict), Tailwind, Supabase (auth + Postgres), Stripe, deployed on Vercel.
Layout:
- `src/app/(marketing)/` public pages, static where possible
- `src/app/(app)/` authenticated product, every route under an auth guard
- `src/app/api/` route handlers, including `src/app/api/stripe/webhook/route.ts`
- `src/components/ui/` primitives, `src/components/` composed features
- `src/lib/supabase/server.ts` server client, `src/lib/supabase/client.ts` browser client
- `src/lib/stripe.ts` Stripe SDK instance and price ID map
- `src/db/schema.ts` Drizzle schema, `src/db/migrations/` generated SQL
- `middleware.ts` session refresh and route protection
Entry points: `src/app/layout.tsx` (root shell), `middleware.ts` (auth), `src/app/api/stripe/webhook/route.ts` (billing state).
## Commands
Use pnpm. Do not use npm or yarn, the lockfile is pnpm-lock.yaml.
```bash
pnpm install # install dependencies
pnpm dev # local dev server on :3000
pnpm build # production build, must pass before any PR
pnpm start # serve the production build
pnpm lint # eslint
pnpm typecheck # tsc --noEmit
pnpm test # vitest run
pnpm test src/lib/billing # run one file or directory
pnpm db:generate # generate a migration from schema.ts
pnpm db:migrate # apply migrations to the current DATABASE_URL
pnpm db:studio # inspect data locally
```
Stripe webhooks locally: `stripe listen --forward-to localhost:3000/api/stripe/webhook`.
Before returning work, run `pnpm typecheck` and `pnpm lint`.
## Code style
- Server components are the default. Add `"use client"` only for state, effects, or browser APIs, and push it as far down the tree as possible.
- Never query the database or read secrets in a client component.
- Data fetching happens in server components or route handlers, never in useEffect.
- Validate every request body and form input with Zod before it reaches the database.
- Use the typed Supabase client from `src/lib/supabase/server.ts`. Do not construct clients inline.
- Absolute imports only, via the `@/` alias. No `../../` chains.
- No `any`. If a type is genuinely unknown use `unknown` and narrow it.
- Tailwind utilities in the markup. No CSS modules, no styled-components, no inline style objects for layout.
- Money is stored and passed around in integer cents, never floats.
- User facing strings live in the component, not in helper files.
## Boundaries
Do not touch without explicit instruction:
- `src/db/migrations/` Migrations are generated, never hand edited, and never deleted once applied.
- `pnpm-lock.yaml` Do not regenerate. Add dependencies with `pnpm add` so the diff stays minimal.
- `.env`, `.env.local`, or any secret value. Add new keys to `.env.example` with an empty value instead.
- `.github/workflows/` and `vercel.json`.
- Stripe price IDs and product configuration.
Needs human review before merge: anything under `src/app/api/stripe/`, changes to `middleware.ts`, row level security policies, and any change to how a subscription tier maps to product access.
If a task appears to require a schema change, propose the schema diff first and wait.
## Testing
- `pnpm test` runs the suite with Vitest. `pnpm test <path>` runs one file.
- Required tests: billing logic (plan resolution, proration, webhook handlers), auth guards, and any pure function in `src/lib/`.
- Webhook handlers are tested against recorded Stripe event fixtures in `src/test/fixtures/stripe/`. Add a fixture rather than mocking the SDK.
- UI tests are not required for presentational components.
- A bug fix ships with a test that fails without the fix.
## Git workflow
- Branch from `main`: `feat/short-description`, `fix/short-description`, `chore/short-description`.
- Conventional commits: `feat(billing): handle subscription downgrade`. Imperative mood, lowercase subject, no trailing period.
- One logical change per commit. Do not mix a refactor with a behavior change.
- Never commit directly to `main` and never force push a shared branch.
- PR description states what changed, why, and how it was verified. Link the issue. Note any migration or env var the reviewer must run.
Each of these is worth a line in your rules file, because the model will otherwise repeat it every session.
Agents add 'use client' at the top of a file the moment they see a hook or an onClick, and they usually add it to the page or layout rather than the leaf component. That pushes the whole subtree into the client bundle and can drag server-only imports along with it. Put a rule in your rules file: default to server components, and only add 'use client' to the smallest leaf that actually needs interactivity, never to a page or layout.
Because getServerSideProps and getStaticProps dominate older training data, agents write them inside app/ files where they do nothing at all, or they scaffold a pages/api handler in a project that uses route handlers. The code often compiles, so the bug is silent. State the router explicitly in your rules file: 'This project uses the App Router only. Never write getServerSideProps, getStaticProps, getInitialProps, or files under pages/.'
When a server-only environment variable comes back undefined in a client component, the fastest fix an agent finds is renaming it to NEXT_PUBLIC_, which inlines the value into the browser bundle. This is how API keys and database URLs leak. Write the rule as a hard stop: 'Never add or rename an env var to NEXT_PUBLIC_. If a value is needed in the browser, move the logic to a server component, route handler, or server action instead.'
Caching defaults changed between Next 14 and Next 15, so agents sprinkle force-cache, revalidate, or dynamic = 'force-dynamic' based on whichever version dominated their training data. The result is either stale pages in production or every request bypassing the cache. Record the installed major version and your caching policy in the rules file, for example: 'Next 15. fetch is uncached by default. Do not add revalidate or force-cache without an explicit instruction.'
A server action is a public HTTP endpoint with a generated ID, callable by anyone who finds it, but agents write them as if only their own form can reach them and skip both the session check and input validation. Any authorization done in the component that renders the form is not authorization at all. Add: 'Every server action must re-check the session and validate its arguments with a schema before touching the database, regardless of what the calling component checks.'
Agents replace plain img tags with next/image as a reflex, then either omit width and height on a non-fill image or point it at a remote host that is not in images.remotePatterns, which fails at runtime rather than at build. List your allowed image hosts in the rules file and add: 'When using next/image with a remote src, confirm the host exists in next.config images.remotePatterns first, and always supply width and height unless the parent is a sized container using fill.'
MCP gives the agent access to systems outside your codebase. These are the ones that pay off in a Next.js project.
| Server | What it does | Why here |
|---|---|---|
| Filesystem MCP server | Gives the agent scoped read and write access to a directory tree. | Next.js projects lean on file-based conventions, so the agent needs to see the real shape of app/ before it decides where a route belongs. |
| Git MCP server | Exposes history, diffs, and blame for the repository. | Diffs are the quickest way for an agent to tell whether this codebase migrated from the Pages Router and which files are leftovers. |
| Playwright MCP server | Drives a real browser so the agent can navigate pages, click, and read the DOM. | Hydration mismatches and client boundary bugs only show up in a running browser, never in the source. |
| A Postgres MCP server | Lets the agent inspect schema and run read queries against your database. | Server components query the database directly, so an agent that can read the real schema stops inventing column names. |
| Sentry MCP server | Pulls issues and stack traces from your Sentry project. | Server component and server action errors surface as opaque digest IDs in production, and Sentry is where the real trace lives. |
Webapp Testing
Tests local web applications using Playwright for verifying frontend functionality, debugging UI behavior, and capturing screenshots.
artifacts-builder
Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui).
Connect
Connect Claude to any app. Send emails, create issues, post messages, update databases - take real actions across Gmail, Slack, GitHub, Notion, and 1000+ services.
great_cto
Claude Code plugin: 7 specialised subagents (tech-lead, senior-dev, qa-engineer, security-officer, devops, l3-support, project-auditor) orchestrating a full SDLC pipeline — architecture, TDD, 12-angle code review, QA, security audit, deploy. 11 project archetypes auto-detected, 13 compliance frameworks (GDPR/PCI-DSS/HIPAA/SOC2/ISO 27001), self-improving knowledge layer that learns from every incident.
iOS Simulator
Enables Claude to interact with iOS Simulator for testing and debugging iOS applications.
Playwright Browser Automation
Model-invoked Playwright automation for testing and validating web applications.
Rules files help, but they cannot fix sprawling architecture. A conventional Next.js codebase gets more out of an agent than a clever one does.