BoilerplateHub

Claude Code Setup for Next.js

Rules file: CLAUDE.md

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.

Configuring Claude Code for Next.js

  • Open CLAUDE.md with the Next major version and the router in use, since that single line prevents most of the wrong answers in this ecosystem.
  • Add a nested CLAUDE.md inside app/ that states the server/client boundary policy, so it loads only when the agent is editing routes.
  • List your build and typecheck commands so the agent verifies its own work instead of declaring a route handler finished after writing it.

What to put in CLAUDE.md

The content is the same across agents, only the filename differs. Copy this, adapt the commands to your repository, and save it as CLAUDE.md.

CLAUDE.md
Download
# 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.

Rule these out for Next.js

These are the failures that repeat across sessions, so each one belongs in CLAUDE.md.

Adding 'use client' to components that do not need it

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.

Mixing App Router and Pages Router patterns

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/.'

Prefixing env vars with NEXT_PUBLIC to silence an error

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.'

Guessing at fetch caching defaults

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.'

Treating server actions as trusted internal code

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.'

Swapping img for next/image without configuration

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.'

Skills that pair with this setup

Same framework, other agents