These docs are a work in progress and may not be fully up to date. Some pages may contain internal notes for our team.
Skip to Content

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 type for type-only imports.
  • Use interface for object shapes; type for unions and aliases.
  • @/ is the path alias for src/. Use it everywhere — no relative ../../ chains across module boundaries.
  • Avoid any. If a type is genuinely unknown, use unknown and 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 use fetchAPI or fetchExt from src/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.ts and a payload transformer in payloads.ts.
  • Pass auditAction, auditResourceType, and auditResourceId on 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.ts re-exports) in components/ — 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.