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