style
esc
  • Loading…

For agentsNext.js Style Guide

Conventions for writing, structuring, formatting and shipping Next.js 16 apps with the App Router, strict TypeScript, Cache Components and StyleX.

Next.js 16.4React 19.3StyleX

The same guide as plain Markdown, built from the same source as the web edition. Rules stay machine-readable: each one starts with its stable ID and level, such as `NEXTJS-02.1` [MUST], so an agent can cite exactly the rule it means.

Rules digest

~7.9k tokens

192 rules, grouped by chapter, no prose or examples. Small enough to keep in an agent's context on every task.

Full guide

~16.8k tokens

Every chapter, table, code sample and drop-in config as one Markdown file. Best when the agent can read files on demand.

Pin a revision

The URLs above always serve the latest text, so an edit to the guide changes what your agent follows. Add @2026.10 after the guide name for a copy that never changes; the snippets below already do. To upgrade, read the changelog for the rules that were added, removed or changed, then bump the revision.

# Revision 2026.10: these files never change
curl -fsSL https://style.chopper.dev/nextjs@2026.10/rules.md
curl -fsSL https://style.chopper.dev/nextjs@2026.10/llms.txt

# What changed between revisions
curl -fsSL https://style.chopper.dev/nextjs/changelog.md

Template project

A ready-to-run Next.js 16.4 app with the guide's src/features layout, the appendix configs, a StyleX design system, an invoices feature (cached queries, Server Actions, forms, loading and error states), a nonce-based CSP and security headers, a signed dev-only sign-in, Vitest and Playwright with axe tests, and CI. Both Markdown files above describe it, so an agent asked to start a new Next.js codebase can fetch a renamed copy instead of scaffolding one by hand.

curl -fsSL "https://style.chopper.dev/nextjs/template/download?name=Project" \
  -o project.zip && unzip project.zip
ParameterDefaultMeaning
nameProjectPascalCase. Becomes the app's display name; lowercased, the package name.

Use it with your tools

MCP server / Claude Code plugin

Connect the remote MCP server, or install the plugin, which adds the server plus a nextjs-style skill that loads these rules whenever Claude writes or reviews Next.js.

claude mcp add --transport http chopper-style https://style.chopper.dev/mcp

# or the plugin: MCP server + one skill per guide
claude plugin marketplace add carson-hopper/chopper.dev
claude plugin install chopper-style@chopper-dev

Claude Code / AGENTS.md

Save the guide into the repo, then point the project instructions file (CLAUDE.md or AGENTS.md) at it.

curl -fsSL https://style.chopper.dev/nextjs@2026.10/llms.txt -o docs/NEXTJS_STYLE.md

# CLAUDE.md / AGENTS.md
## Next.js style
All Next.js code follows docs/NEXTJS_STYLE.md (rev. 2026.10). Rules marked
[MUST] / [MUST NOT] are non-negotiable; [SHOULD] rules need a comment when broken.
Cite rules by ID (NEXTJS-02.1) in reviews and comments.

Cursor

A project rule that applies the digest whenever a Next.js file is in play.

mkdir -p .cursor/rules
{ printf -- '---\ndescription: Next.js Style Guide\nglobs: "**/*.{ts,tsx,js,jsx,mjs}"\nalwaysApply: false\n---\n\n'
  curl -fsSL https://style.chopper.dev/nextjs@2026.10/rules.md; } > .cursor/rules/nextjs-style.mdc

Any chat assistant

Paste this ahead of the request, or attach the digest file.

Follow the Next.js Style Guide at https://style.chopper.dev/nextjs@2026.10/rules.md for all Next.js code you write or review. Treat MUST / MUST NOT as hard constraints and cite the rule's ID (such as NEXTJS-02.1) when you flag a violation.

Digest preview

# Next.js Style Guide: rules digest

> Every rule from the Next.js Style Guide (rev. 2026.10), without the prose, tables and examples. The full guide: https://style.chopper.dev/nextjs/llms.txt

Revision 2026.10, plus edits not yet released. This URL always serves the latest text; pin a revision with https://style.chopper.dev/nextjs@2026.10/rules.md. Changelog: https://style.chopper.dev/nextjs/changelog.md.

MUST / MUST NOT are non-negotiable. SHOULD / SHOULD NOT are the default; deviate only with a short comment saying why. MAY is allowed when it helps. AVOID is allowed but a smell. Cite a rule by its ID (NEXTJS-02.1): IDs never change or get reused, and https://style.chopper.dev/nextjs#NEXTJS-02.1 links to the rule. Code samples use a placeholder app called Project with an `invoices` feature. Substitute your own names. When the guide is silent, match the surrounding code, then the official Next.js documentation and React documentation, in that order.

## Starter project

A ready-to-run Next.js 16.4 app with the guide's src/features layout, the appendix configs, a StyleX design system, an invoices feature (cached queries, Server Actions, forms, loading and error states), a nonce-based CSP and security headers, a signed dev-only sign-in, Vitest and Playwright with axe tests, and CI. It is generated from this guide and follows it, so prefer it over scaffolding a new codebase by hand.

Download it as a zip, renamed to the user's names, with a GET request (query parameters are optional; missing ones take their defaults, invalid ones return HTTP 400 with a message):

```bash
curl -fsSL "https://style.chopper.dev/nextjs/template/download?name=Project" -o project.zip && unzip project.zip
```

Parameters:

- `name`: Project name. PascalCase. Becomes the app's display name; lowercased, the package name. Must match `^(?:[A-Z][A-Za-z0-9]{1,31})$`. Default `Project`.

After unzipping:

1. **Install.** With Node.js 24 and corepack enabled, run pnpm install, then copy .env.example to .env.local.
2. **Run.** pnpm dev serves the app; pnpm lint, typecheck, test and build run the same checks as CI.

## 02. Baseline and toolchain

- `NEXTJS-02.1` **MUST** enable Cache Components and Partial Prefetching (`cacheComponents: true`, `partialPrefetching: true`) in new apps. Existing apps migrate to them as one tracked task.
- `NEXTJS-02.2` **MUST** pin Node.js in `.nvmrc` and the `engines` field, and pnpm in `packageManager`, so local and CI match.
- `NEXTJS-02.3` **MUST** commit `pnpm-lock.yaml` and install with `pnpm install --frozen-lockfile` in CI.
- `NEXTJS-02.4` **MUST NOT** use features flagged `experimental` in `next.config.ts` in production apps without an ADR naming the flag and the exit plan.
- `NEXTJS-02.5` **SHOULD** upgrade minor Next.js releases within a month and apply security releases within days. `next upgrade` and the codemods do most of the work.
- `NEXTJS-02.6` **SHOULD** enable the React Compiler (`reactCompiler: true`) and drop manual `useMemo` / `useCallback` / `memo` except where a profile shows they still help.

## 03. Project layout

- `NEXTJS-03.1` **MUST** use the `src/` directory and the App Router. Route files (`page.tsx`, `layout.tsx` and friends) live only under `src/app`.
- `NEXTJS-03.2` **SHOULD** keep route files short: read params, call a feature query, render feature components. Logic moves to `src/features/<feature>/`.
- `NEXTJS-03.3` **SHOULD** use route groups `(name)` to give sections different layouts without changing URLs.
- `NEXTJS-03.4` **SHOULD** colocate everything a feature needs (components, actions, queries, schemas, tests) in its feature folder.
- `NEXTJS-03.5` **MUST** put tests next to the file they test, named `<name>.test.ts` or `<name>.test.tsx`. End-to-end tests live in `e2e/`.
- `NEXTJS-03.6` **SHOULD** keep `src/components/ui` for generic, design-system primitives (button, dialog, input) that know nothing about features.
- `NEXTJS-03.7` **AVOID** `utils`, `helpers` or `common` folders. Code belongs to the feature that uses it; promote it to `src/lib` only when three features need it.
- `NEXTJS-03.8` **MUST** import across the tree with the `@/` alias (`@/features/invoices/queries`), never long relative paths like `../../../lib/db`.
- `NEXTJS-03.9` **SHOULD** prefix folders inside `src/app` that are not routes with `_` (`_components`) when something must be colocated there, so they are never routable.

## 04. Naming and file conventions

- `NEXTJS-04.1` **MUST** name a component file after its main component in kebab-case: `InvoiceTable` lives in `invoice-table.tsx`.
- `NEXTJS-04.2` **SHOULD** export one main component per file. Small private sub-components may stay in the same file.
- `NEXTJS-04.3` **MUST** use named exports everywhere except where Next.js requires a default export (pages, layouts, special files, `next.config.ts`).
- `NEXTJS-04.4` **MUST** name props types `<Component>Props`.
- `NEXTJS-04.5` **MUST** write acronyms as one capitalized word: `getHttpClient`, `InvoicePdf`, not `getHTTPClient`.
- `NEXTJS-04.6` **SHOULD** make a name as descriptive as its scope is wide, and never abbreviate by deleting letters (`customer`, not `cstmr`).

## 05. Formatting and linting

- `NEXTJS-05.1` **MUST** run `prettier --check` and `eslint --max-warnings 0` in CI. A warning that cannot be fixed is disabled on the narrowest line with a comment saying why.
- `NEXTJS-05.2` **MUST** use the ESLint flat config (`eslint.config.mjs`) with the Next.js plugin's `core-web-vitals` rules, `typescript-eslint` type-aware rules, and the React Hooks rules.
- `NEXTJS-05.3` **MUST NOT** disable a lint rule for a whole file or project to make one warning go away.
- `NEXTJS-05.4` **MUST NOT** use default exports outside the places Next.js requires them; the lint config enforces this.
- `NEXTJS-05.5` **MUST** use `import type` for imports used only as types, so they are erased from the bundle.
- `NEXTJS-05.6` **MUST NOT** use barrel files (`index.ts` that re-exports a folder) inside the app; they slow builds and drag unused code into bundles. Import from the file that defines the thing.

## 06. TypeScript

- `NEXTJS-06.1` **MUST** enable `strict`, `noUncheckedIndexedAccess`, `noImplicitOverride`, `exactOptionalPropertyTypes` and `verbatimModuleSyntax` (full `tsconfig.json` in the appendix).
- `NEXTJS-06.2` **MUST** keep `pnpm typecheck` (`tsc --noEmit`) clean in CI.
- `NEXTJS-06.3` **MUST NOT** use `any`. Use `unknown` and narrow it, or write the real type.
- `NEXTJS-06.4` **MUST NOT** use non-null assertions (`value!`) or `as` casts to silence the compiler. The exceptions are `as const` and narrowing a value you have just validated.
- `NEXTJS-06.5` **SHOULD** use `type` for unions, props and most shapes, and `interface` only when declaration merging or `extends` chains are needed. Be consistent within a feature.
- `NEXTJS-06.6` **SHOULD** derive types instead of repeating them: `z.infer<typeof schema>`, `ReturnType<typeof fn>`, `Awaited<...>`, `ComponentProps<typeof Button>`.
- `NEXTJS-06.7` **SHOULD** use `satisfies` to check an object against a type while keeping its literal types.
- `NEXTJS-06.8` **SHOULD** model states as discriminated unions (`{ status: 'idle' } | { status: 'error'; message: string }`) rather than several optional flags.
- `NEXTJS-06.9` **MUST** handle every member of a union in a `switch`, ending with an exhaustiveness check.
- `NEXTJS-06.10` **AVOID** `enum`; use a union of string literals or an `as const` object.
- `NEXTJS-06.11` **MUST** parse untrusted input with a Zod schema at the boundary: Server Action arguments, Route Handler bodies, `searchParams`, webhook payloads, and responses from external APIs.
- `NEXTJS-06.12` **MUST** validate environment variables once at startup in `src/lib/env.ts` and import them from there, never from `process.env` directly.
- `NEXTJS-06.13` **SHOULD** keep each schema next to the feature that owns it (`features/invoices/schemas.ts`) and export the inferred type beside it.

## 07. Server and Client Components

- `NEXTJS-07.1` **SHOULD** fetch data, read cookies and headers, and talk to the database in Server Components, close to where the data is rendered.
- `NEXTJS-07.2` **MUST** mark modules that must never reach the browser (database clients, secrets, server-side SDKs) with `import 'server-only';` at the top, so a mistaken client import fails the build.
- `NEXTJS-07.3` **SHOULD** pass Server Components into Client Components as `children` or props rather than importing them inside client files, which would turn them into client code.
- `NEXTJS-07.4` **MUST** add `'use client'` only to files that need `useState`, `useReducer`, effects, event handlers, browser APIs, or client-only libraries.
- `NEXTJS-07.5` **SHOULD** push the boundary as far down the tree as possible: make the button interactive, not the whole page.
- `NEXTJS-07.6` **MUST** pass only serializable props from server to client (plain data, Server Action references, JSX). Never pass class instances, functions other than Server Actions, or database records with methods.
- `NEXTJS-07.7` **MUST NOT** put secrets, internal IDs that grant access, or full database rows into client props. Select only the fields the UI shows; everything passed is visible in the page payload.
- `NEXTJS-07.8` **MUST NOT** fetch data in `useEffect` when a Server Component could fetch it.
- `NEXTJS-07.9` **SHOULD** use `next/dynamic` with `ssr: false` only for components that genuinely cannot render on the server (canvas, map widgets).

## 08. Routing and layouts

- `NEXTJS-08.1` **MUST** use lowercase kebab-case segments that read as nouns: `/invoices/[invoiceId]/edit`, not `/getInvoice?id=`.
- `NEXTJS-08.2` **MUST** `await` `params` and `searchParams` (they are Promises since Next.js 16), and type them with the generated `PageProps<'/route'>` and `LayoutProps<'/route'>` helpers.
- `NEXTJS-08.3` **MUST** validate `searchParams` with a Zod schema before use; they are user input.
- `NEXTJS-08.4` **SHOULD** enable typed routes (`typedRoutes: true`) so `<Link href>` and `router.push` are checked at compile time.
- `NEXTJS-08.5` **SHOULD** implement `generateStaticParams` for dynamic segments whose values are known at build time.
- `NEXTJS-08.6` **MUST** keep the root layout minimal: `<html>`, `<body>`, fonts, global providers and global styles.
- `NEXTJS-08.7` **SHOULD** use route-group layouts for each distinct shell (marketing site, signed-in app, admin).
- `NEXTJS-08.8` **MUST NOT** rely on a layout to protect its child pages. Layouts do not re-render on every navigation; check authorization in each page, action and handler (section 15).
- `NEXTJS-08.9` **MUST** export `metadata` or `generateMetadata` from every page that is reachable without signing in, with a unique title and description.
- `NEXTJS-08.10` **SHOULD** give every segment that fetches slow data a `loading.tsx`, or wrap the slow part in `<Suspense>` with a skeleton that matches the final layout.
- `NEXTJS-08.11` **MUST** give each top-level section an `error.tsx` (a Client Component with a retry button) and the app a `global-error.tsx`.
- `NEXTJS-08.12` **MUST** call `notFound()` when a requested record does not exist, rather than rendering an empty page.
- `NEXTJS-08.13` **MAY** use parallel routes (`@slot`) and intercepting routes (`(.)photo`) for modals and split views that need their own URLs. Each slot needs a `default.tsx`.
- `NEXTJS-08.14` **AVOID** them for anything a plain component would do; they add files and edge cases.

## 09. Data fetching and caching

- `NEXTJS-09.1` **MUST** read data in Server Components or in query functions in `features/<feature>/queries.ts`, marked `import 'server-only'`.
- `NEXTJS-09.2` **MUST NOT** call your own Route Handlers from Server Components (`fetch('/api/invoices')`); call the query function directly.
- `NEXTJS-09.3` **SHOULD** start independent requests in parallel (`Promise.all`) rather than awaiting them one after another.
- `NEXTJS-09.4` **SHOULD** wrap request-scoped reads in React `cache()` when several components in one render need the same data (for example the current user).
- `NEXTJS-09.5` **MUST** read request-time data (`cookies()`, `headers()`, `params`, `searchParams`, uncached queries) inside a `<Suspense>` boundary or under a `loading.tsx`, so the static shell can be served immediately.
- `NEXTJS-09.6` **SHOULD** place each Suspense boundary around the smallest part that is slow, with a skeleton the same size as the real content.
- `NEXTJS-09.7` **SHOULD** mark a component or query `'use cache'` when its output depends only on its arguments and changes rarely: product catalogs, published content, reference data.
- `NEXTJS-09.8` **MUST** give every `'use cache'` scope a `cacheLife` profile (`'minutes'`, `'hours'`, `'days'`, `'max'` or a named custom profile) and at least one `cacheTag` for the data it depends on.
- `NEXTJS-09.9` **MUST NOT** read `cookies()`, `headers()` or other request data inside a `'use cache'` scope. Read them outside and pass the values in as arguments; arguments become part of the cache key.
- `NEXTJS-09.10` **MUST NOT** cache per-user data in a shared cache. Pass the user ID as an argument, or do not cache it.
- `NEXTJS-09.11` **SHOULD** name tags `<entity>` and `<entity>:<id>` (`invoices`, `invoice:42`) so invalidation is predictable.
- `NEXTJS-09.12` **MAY** use `export const ensureStatic = 'navigation'` on marketing, docs and blog layouts to fail the build if dynamic content sneaks in.
- `NEXTJS-09.13` **MAY** use `await navigation()` or `await prefetch()` from `next/cache` to keep expensive parts out of link prefetches.

## 10. Mutations: Server Actions and Route Handlers

- `NEXTJS-10.1` **MUST** define actions in `features/<feature>/actions.ts` with `'use server'` at the top of the file, not inline inside components.
- `NEXTJS-10.2` **MUST** treat every action as a public POST endpoint: anyone can call it with any arguments. Authenticate, authorize the specific record, and parse arguments with Zod at the start of every action.
- `NEXTJS-10.3` **MUST** return a typed result (`{ ok: true, data } | { ok: false, error, fieldErrors? }`) for expected failures such as validation errors, rather than throwing. Throw only for unexpected failures.
- `NEXTJS-10.4` **MUST** invalidate what changed before returning: `updateTag` for data the user is looking at, `revalidateTag(tag, 'max')` for data others see.
- `NEXTJS-10.5` **SHOULD** call `redirect()` after a successful create when the user should land on the new record. Call it outside `try` / `catch`; it works by throwing.
- `NEXTJS-10.6` **MUST NOT** return more data than the UI needs; the return value is sent to the browser.
- `NEXTJS-10.7` **SHOULD** use Route Handlers only for callers that are not the app's own React UI: webhooks, public APIs, OAuth callbacks, file and feed responses.
- `NEXTJS-10.8` **MUST** verify webhook signatures before reading the payload.
- `NEXTJS-10.9` **MUST** parse request bodies and query strings with Zod and return proper status codes (400 invalid input, 401/403 auth, 404, 409 conflict, 422, 500).
- `NEXTJS-10.10` **MUST NOT** place a `route.ts` and a `page.tsx` in the same segment.

## 11. Components and styling

- `NEXTJS-11.1` **SHOULD** keep a component under about 150 lines and focused on one job. Split when a component renders unrelated sections or mixes data loading with layout.
- `NEXTJS-11.2` **MUST** type props with a `<Component>Props` type and destructure them in the signature.
- `NEXTJS-11.3` **SHOULD** compose with `children` and slot props rather than adding boolean props for every variant. More than three or four boolean props is a sign the component should split.
- `NEXTJS-11.4` **MUST** give list items a stable `key` from the data, never the array index when the list can reorder.
- `NEXTJS-11.5` **SHOULD** build feature components from `src/components/ui` primitives rather than raw HTML styled from scratch.
- `NEXTJS-11.6` **MUST NOT** define components inside other components; they re-mount on every render.
- `NEXTJS-11.7` **MUST** compile StyleX with `@stylexjs/babel-plugin` and `@stylexjs/postcss-plugin`, configured in `babel.config.js` and `postcss.config.js` (appendix). This works with Turbopack from Next.js 16.0.3.
- `NEXTJS-11.8` **MUST** keep a single CSS entry (`src/styles/globals.css`) containing `@stylex;` once, imported from the root layout, with `useCSSLayers: true` so StyleX rules layer predictably over resets.
- `NEXTJS-11.9` **MUST** run `@stylexjs/eslint-plugin` with `valid-styles`, `no-unused`, `valid-shorthands` and `sort-keys`. The compiler accepts invalid styles; the linter is what catches them.
- `NEXTJS-11.10` **SHOULD** know that a `babel.config.js` makes Next.js compile app code with Babel instead of SWC. Builds are slower; this is the accepted cost of StyleX (and the React Compiler uses Babel anyway).
- `NEXTJS-11.11` **MUST** define styles with `stylex.create` at module level, below the component that uses them, and apply them with `{...stylex.props(...)}`.
- `NEXTJS-11.12` **MUST NOT** use `className` strings, inline `style` objects for static styles, CSS Modules, or another CSS-in-JS library alongside StyleX. Inline `style` is allowed only for truly dynamic values (a measured width), and dynamic StyleX functions are preferred over it.
- `NEXTJS-11.13` **MUST** use design tokens from `stylex.defineVars` for colors, spacing, radii, type sizes, shadows and z-index. Hard-coded hex values and pixel sizes in feature code are not allowed.
- `NEXTJS-11.14` **MUST** put tokens in files named `*.stylex.ts` (for example `src/styles/tokens.stylex.ts`), exporting only `defineVars` / `defineConsts` results; the compiler requires this.
- `NEXTJS-11.15` **SHOULD** create themes (dark mode, brand variants) with `stylex.createTheme` over the same variables, applied at the layout level.
- `NEXTJS-11.16` **SHOULD** write conditional styles per property: `color: { default: tokens.text, ':hover': tokens.textStrong, '@media (prefers-color-scheme: dark)': tokens.textDark }`.
- `NEXTJS-11.17` **SHOULD** let components accept a `style?: stylex.StyleXStyles` prop for caller overrides instead of exposing many visual props, and merge it last: `stylex.props(styles.base, style)`.
- `NEXTJS-11.18` **SHOULD** name style keys by role (`root`, `header`, `label`, `active`), not by appearance (`blueText`, `bigPadding`).

## 12. State management

- `NEXTJS-12.1` **MUST NOT** copy server data into client state and keep it in sync by hand. Mutate with a Server Action and let the server re-render.
- `NEXTJS-12.2` **SHOULD** keep filters, sorting, pagination and selected tabs in the URL, read on the server through `searchParams` and updated on the client with `useRouter` / `<Link>`.
- `NEXTJS-12.3` **SHOULD** use `useOptimistic` for instant feedback on mutations instead of maintaining a parallel client copy of the data.
- `NEXTJS-12.4` **SHOULD** put context providers as deep in the tree as possible, never wrapping the whole app for state one feature uses.
- `NEXTJS-12.5` **AVOID** global client stores. Adding one needs a reason in an ADR.
- `NEXTJS-12.6` **MUST NOT** use `useEffect` to derive state from props or other state; compute it during render.
- `NEXTJS-12.7` **SHOULD** use `useEffectEvent` for non-reactive logic inside effects instead of disabling the exhaustive-deps lint rule.

## 13. Forms

- `NEXTJS-13.1` **MUST** submit forms with `<form action={serverAction}>` and read the result with `useActionState`, not with `onSubmit` handlers that call `fetch`.
- `NEXTJS-13.2` **MUST** validate on the server with the same Zod schema as the action, even when the client also validates. Client validation is a convenience, never a guarantee.
- `NEXTJS-13.3` **SHOULD** use native HTML constraints (`required`, `type="email"`, `min`, `maxLength`) for instant client feedback.
- `NEXTJS-13.4` **MUST** show field errors next to their fields and link them with `aria-describedby`, and show a form-level error for anything else.
- `NEXTJS-13.5` **MUST** disable the submit button and show a pending label while the action runs (`isPending` from `useActionState`, or `useFormStatus` inside a submit-button component).
- `NEXTJS-13.6` **SHOULD** keep the user's input after a failed submit by returning it from the action or using `defaultValue` from the previous state.
- `NEXTJS-13.7` **SHOULD** use a form library (React Hook Form) only for forms with complex client behavior: dynamic field arrays, multi-step flows, live cross-field validation.

## 14. Errors and loading states

- `NEXTJS-14.1` **MUST** give `error.tsx` a short human message, a retry button (`reset`), and no stack trace or raw error text. The error `digest` may be shown so support can find the log entry.
- `NEXTJS-14.2` **MUST** report unexpected errors to the error tracker (Sentry or equivalent) through `instrumentation.ts` / `onRequestError`, not by adding `try` / `catch` to every component.
- `NEXTJS-14.3` **MUST NOT** catch an error only to render nothing. Either handle it with a visible state or let it reach the boundary.
- `NEXTJS-14.4` **MUST** keep error messages shown to users free of internal details: table names, IDs of other users, stack traces.
- `NEXTJS-14.5` **SHOULD** use skeletons that match the final layout's size, so the page does not jump when data arrives.
- `NEXTJS-14.6` **SHOULD** stream: render the shell immediately and suspend only the parts that wait (section 9).
- `NEXTJS-14.7` **MUST** show pending state on every button that triggers an action, and prevent double submission.
- `NEXTJS-14.8` **AVOID** full-page spinners; they hide content that is already available.

## 15. Security

- `NEXTJS-15.1` **MUST** route every database read and write through a data-access function that takes the current user and enforces ownership or role (`findOwned(invoiceId, user.id)`).
- `NEXTJS-15.2` **MUST** call `requireUser()` (or equivalent) at the top of every Server Action, Route Handler and page that shows private data. A check in a layout or in `proxy.ts` alone is not enough.
- `NEXTJS-15.3` **SHOULD** use `proxy.ts` only for cheap, coarse decisions: redirect signed-out users away from app routes, set headers, rewrite locales. It must not query the database for authorization.
- `NEXTJS-15.4` **MUST** use an established auth library (Auth.js, Better Auth, Clerk or the platform's identity provider) and never hand-roll session cookies or password hashing.
- `NEXTJS-15.5` **MUST** set session cookies `HttpOnly`, `Secure` and `SameSite=Lax` (or `Strict`).
- `NEXTJS-15.6` **MUST** keep secrets in server-only variables (no `NEXT_PUBLIC_` prefix). Anything prefixed `NEXT_PUBLIC_` is inlined into the browser bundle.
- `NEXTJS-15.7` **MUST** import environment variables only from `src/lib/env.ts`, which validates them with Zod and is marked `server-only` (with a separate validated module for public variables).
- `NEXTJS-15.8` **MUST** list every variable in `.env.example` with a description and no real value. [MUST NOT] commit `.env` files with real values.
- `NEXTJS-15.9` **MUST** select only the fields a component renders before passing data to a Client Component or returning it from an action. Use a data-transfer type, never the raw database row.
- `NEXTJS-15.10` **SHOULD** use React's taint APIs (`experimental_taintObjectReference`) only if enabled for the project; otherwise rely on explicit field selection.
- `NEXTJS-15.11` **MUST** send a Content Security Policy with nonces, plus `Strict-Transport-Security`, `X-Content-Type-Options: nosniff`, `Referrer-Policy` and a restrictive `Permissions-Policy`.
- `NEXTJS-15.12` **MUST NOT** use `dangerouslySetInnerHTML` except with content sanitized by a vetted library (DOMPurify), with a comment.
- `NEXTJS-15.13` **MUST** configure `images.remotePatterns` narrowly (exact hosts and paths), never a wildcard host.
- `NEXTJS-15.14` **MUST** rate-limit sign-in, sign-up, password reset and any action that sends email or costs money.
- `NEXTJS-15.15` **SHOULD** apply Next.js security releases within days of publication and subscribe to the Next.js security advisories.

## 16. Performance

- `NEXTJS-16.1` **SHOULD** keep `'use client'` boundaries at the leaves (section 7); every client file adds to the bundle.
- `NEXTJS-16.2` **SHOULD** load heavy, below-the-fold or interaction-only components with `next/dynamic` or a dynamic `import()` on demand.
- `NEXTJS-16.3` **MUST** check the bundle analyzer before adding a dependency over about 20 kB gzipped, and prefer a lighter one or a platform API.
- `NEXTJS-16.4` **MUST NOT** import a whole library for one function (`lodash`, icon packs); import the specific module.
- `NEXTJS-16.5` **SHOULD** rely on the React Compiler for memoization and remove manual `useMemo` / `useCallback` unless a profile shows a gain.
- `NEXTJS-16.6` **MUST** render images with `next/image`, with explicit `width` and `height` (or `fill` with a sized parent) and a meaningful `sizes` value for responsive images.
- `NEXTJS-16.7` **SHOULD** set `priority` (or `preload`) only on the image that is the LCP element of the page.
- `NEXTJS-16.8` **MUST** load fonts with `next/font`, self-hosted, with `display: 'swap'`, and only the weights in use.
- `NEXTJS-16.9` **MUST** load third-party scripts with `next/script` and the latest-loading strategy that works (`lazyOnload` for analytics and chat widgets).
- `NEXTJS-16.10` **SHOULD** make every page with no per-user content fully static or cached, and keep request-time rendering to the parts that need it.
- `NEXTJS-16.11` **SHOULD** use `<Link>` for internal navigation so routes are prefetched, and `prefetch` explicitly on links users are likely to click next.
- `NEXTJS-16.12` **MUST** reserve space for anything that loads late (images, embeds, ads, skeletons) so the layout does not shift.

## 17. Accessibility

- `NEXTJS-17.1` **MUST** use the native element for the job: `<button>` for actions, `<a>` / `<Link>` for navigation, `<label>` for every form control, headings in order (one `<h1>` per page).
- `NEXTJS-17.2` **MUST NOT** attach click handlers to `<div>` or `<span>`. If something is clickable, it is a button or a link.
- `NEXTJS-17.3` **MUST** give every image meaningful `alt` text, or `alt=""` when it is decorative.
- `NEXTJS-17.4` **MUST** make every interaction work with the keyboard alone, with a visible focus style (never `outline: none` without a replacement).
- `NEXTJS-17.5` **MUST** meet contrast ratios of 4.5:1 for body text and 3:1 for large text and UI components. Check the design tokens once, and every use inherits it.
- `NEXTJS-17.6` **MUST** announce form errors and async results: link field errors with `aria-describedby`, and use `role="alert"` or an `aria-live` region for messages that appear after submit.
- `NEXTJS-17.7` **MUST** build dialogs, menus, comboboxes and tabs from an accessible primitive library (Radix, React Aria, Base UI) or the native `<dialog>`, not from scratch.
- `NEXTJS-17.8` **SHOULD** respect `prefers-reduced-motion` for animations and view transitions.
- `NEXTJS-17.9` **MUST** set `lang` on `<html>` and a unique, descriptive `<title>` per page through metadata.
- `NEXTJS-17.10` **SHOULD** move focus to the main heading or content after client navigations that replace the page, and to the first error after a failed submit.
- `NEXTJS-17.11` **MUST** run automated checks (axe through Playwright) in CI, and test new flows with a screen reader before release.

## 18. Testing

- `NEXTJS-18.1` **MUST** put each unit or component test next to the file it tests, named `<name>.test.ts(x)`.
- `NEXTJS-18.2` **MUST** query the DOM the way a user would: by role, label and text (`getByRole('button', { name: 'Create invoice' })`), not by test IDs or class names. Use `data-testid` only when nothing else identifies the element.
- `NEXTJS-18.3` **MUST** test Server Actions as functions: call them with a `FormData` and assert on the result and the data-layer fake, including the unauthorized and invalid-input cases.
- `NEXTJS-18.4` **SHOULD** cover each critical user flow (sign in, create, edit, pay, delete) with one Playwright test that runs against `next build && next start`.
- `NEXTJS-18.5` **MUST** keep tests deterministic: fixed clock, seeded data, no real third-party calls (mock them with MSW at the network layer).
- `NEXTJS-18.6` **MUST** add a failing test that reproduces a bug before fixing it.
- `NEXTJS-18.7` **SHOULD** run Playwright's axe check on every page covered by an end-to-end test.
- `NEXTJS-18.8` **AVOID** snapshot tests of whole components; they break on every change and are approved without reading.

## 19. Build, configuration and deployment

- `NEXTJS-19.1` **MUST** write the Next.js config as `next.config.ts` typed with `NextConfig`, and keep it small: every option has a comment saying why it is there.
- `NEXTJS-19.2` **MUST** run lint, format check, typecheck, unit tests and build on every pull request; `next build` no longer lints for you.
- `NEXTJS-19.3` **MUST** configure Vitest with the StyleX Babel plugin so components that use StyleX render in tests.
- `NEXTJS-19.4` **MUST** keep all configuration that differs per environment in environment variables, validated at startup (section 15). Removed options like `publicRuntimeConfig` are not used.
- `NEXTJS-19.5` **SHOULD** use `output: 'standalone'` when deploying in a container, with a multi-stage Dockerfile that copies only `.next/standalone`, `.next/static` and `public`.
- `NEXTJS-19.6` **SHOULD** configure a shared cache handler (Redis or the platform's) for `'use cache'` when running more than one instance, so instances do not serve different cached data.
- `NEXTJS-19.7` **MUST** give every deployment a health check route (`/api/health`) that checks dependencies without exposing details.
- `NEXTJS-19.8` **SHOULD** let Renovate or Dependabot propose dependency updates weekly, with Next.js and React updated together.
- `NEXTJS-19.9` **MUST** keep `experimental` flags out of production config unless an ADR names the flag and the plan to remove it.

## 20. Comments and documentation

- `NEXTJS-20.1` **SHOULD** give every exported function, hook, Server Action and `components/ui` primitive a `/** */` TSDoc comment starting with a verb phrase: "Returns the invoices a user can see."
- `NEXTJS-20.2` **SHOULD** document what callers need: required permissions, what happens when nothing is found, caching behavior (`cacheLife`, tags), and side effects (emails sent, cache invalidated).
- `NEXTJS-20.3` **MUST NOT** repeat the type signature in prose (`@param id - the id`). Document a parameter only when its meaning or constraints are not obvious from its name and type.
- `NEXTJS-20.4` **MUST** write implementation comments with `//` and explain why when the code does something surprising: a workaround for a framework bug (with a link), an ordering requirement, a performance trick.
- `NEXTJS-20.5` **MUST** write TODOs as `// TODO: <issue link> - <what needs doing>`.
- `NEXTJS-20.6` **AVOID** commented-out code. Git remembers it.
- `NEXTJS-20.7` **MUST** keep `README.md` current with how to install, run, test and deploy, and the list of environment variables (pointing at `.env.example`).
- `NEXTJS-20.8` **SHOULD** record significant decisions (auth library, database, caching strategy, a global store) as short ADRs in `docs/adr/NNNN-title.md`.

## 21. Git and review

- `NEXTJS-21.1` **MUST** use the feature folder as the scope (`invoices`, `auth`, `billing`).
- `NEXTJS-21.2` **MUST** keep formatting-only and rename-only changes in their own commits.
- `NEXTJS-21.3` **SHOULD** mark breaking changes with `!` and a `BREAKING CHANGE:` footer.