Next.js — conventions
The canonical source of truth is AGENTS.md in the Next.js repo root. This page expands on it with context and examples that link to surrounding architecture. Refer to AGENTS.md for the definitive ruleset.
CSS naming — BEM
All CSS class names use BEM (block__element--modifier). When writing new styles, BEM is required. When touching existing styles that violate BEM, fix them in the same pass.
// correct
.finderCard__title--highlighted { }
// wrong
.finder-card-title-highlighted { }Component styles live in .module.scss files co-located with the component. Globals live in src/sass/. Never put styles in a global file if they belong to a single component.
TypeScript discipline
- Always use
import typefor type-only imports. - Use
interfacefor object shapes;typefor unions and aliases. @/is the path alias forsrc/. Use it everywhere — no relative../../chains across module boundaries.- Avoid
any. If a type is genuinely unknown, useunknownand narrow before use.
Shared domain types live in src/types/. Types that are specific to a single component or module stay in that file.
API layer
- Never call
fetch()directly. Always usefetchAPIorfetchExtfromsrc/lib/api/core.ts. - New resource calls belong in the appropriate domain file under
src/lib/api/. - New resource types need a normalizer entry in
normalizers.tsand a payload transformer inpayloads.ts. - Pass
auditAction,auditResourceType, andauditResourceIdon mutations that map to a meaningful user-facing action.
Mutations and query invalidation
Use TanStack Query’s useMutation for all write operations. Always invalidate relevant query keys in onSuccess — stale cache after a write causes confusing UI.
const mutation = useMutation({
mutationFn: (data) => updateFinder(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['finder', id] });
queryClient.invalidateQueries({ queryKey: ['finders'] });
},
});Query key conventions: arrays starting with the resource noun (['finder', id], ['finders'], ['locations', finderId]). Keep keys consistent across the codebase — a mismatch means invalidation misses its target.
Error surfacing
Surface errors to the user via addAlert(message, 'error') from useUiStore. Do not use local error state, console.error, or silent swallowing as a substitute for user-facing feedback in the Next.js app.
The getErrorMessageFromData helper in core.ts extracts the best available message from an API error response. Use it before passing to addAlert.
SCSS design tokens
Use $v2-* SCSS variables for all colors. These resolve to CSS custom properties defined in :root and [data-theme='dark'] in globals.scss. Never hardcode hex values or pixel values that map to an existing token.
Use color-mix(in srgb, ...) for semi-transparent variants when a dedicated token doesn’t exist.
Component file shape
- One component per file.
- Named exports for hooks and utilities; default export for the primary component.
- No barrel files (
index.tsre-exports) incomponents/— they make tree-shaking harder and obscure import origins.
Comments
Add a comment only when the reason is non-obvious: a hidden constraint, a subtle invariant, a framework quirk. Never describe what the code does — the code does that. One to three lines max.
Testing
There is no unit or integration test suite in this repo today. See how-to: test for context on adding tests.
Related pages
- Next.js: API layer — API discipline detail
- Next.js: Design system — SCSS token system
- Next.js: State management — Zustand + TanStack Query patterns