Skip to content

Production Frontend Agent Rules (AGENTS-frontend.md) ​

Copy this file directly into your target frontend project root as AGENTS.md or append it to your existing project rules. Universal production baseline for frontend web applications, using React ecosystem examples throughout (adapt equivalents for Vue/Svelte/Solid). Framework or meta-framework-specific rules should be appended directly inside the project-level AGENTS.md file.

1. Component Architecture & Layering ​

  • [ ] Strict Presentation/Container Separation: Presentational (UI) components MUST be pure, deterministic functions driven strictly by props. Business logic, side-effects, and data fetching MUST be isolated in container components or custom hooks.
  • [ ] Props Immutability & Contract Discipline: Props MUST be treated as immutable. Component prop contracts MUST use explicit, strict TypeScript types (interface/type), avoiding generic any or loose Record<string, any>.
  • [ ] Single Responsibility & Line-Cap Limits: Component functions MUST NOT exceed ~150 lines of code or manage more than 3 unrelated pieces of state. Complex components MUST be refactored into focused sub-components or custom hooks (co-located sub-components in the same module are permitted when focused).
  • [ ] Named Exports Baseline: Use explicit named exports for UI components to ensure refactoring safety and tree-shaking consistency (reserving default exports solely for framework-required dynamic route entries).

2. State Management & Data Fetching ​

  • [ ] Server State vs. Client UI State Decoupling: Remote server data MUST be managed exclusively by dedicated async data-fetching caches (e.g. TanStack Query / SWR). Server-prefetched or SSR data MUST be hydrated into client caches via formal hydration boundaries (e.g. HydrationBoundary) rather than prop drilling into component state. Ephemeral UI state (modals, drawer toggles, active tabs) MUST use lightweight client state tools (e.g. Zustand, React Context).
  • [ ] Zero State Duplication: Copying API response data into local component state (useState) or global UI stores for read-only display is strictly forbidden. Derived state MUST be calculated on-the-fly or via memoized selectors (useMemo). Localized edit buffers (e.g. form library defaultValues) are permitted for user input capture.
  • [ ] Mutation Lifecycle & Invalidation: Data mutations MUST declare explicit cache invalidation keys using structured array tuples or type-safe query key factories (e.g. queryKeys.users.detail(id) / ['users', 'detail', id]) or optimistic updates with automatic error rollback handlers. Hard page reloads (window.location.reload()) to refresh state are strictly banned — instead, use the data-fetching library's explicit cache invalidation APIs (e.g. queryClient.invalidateQueries()).
  • [ ] Query Hygiene & Error Boundaries: Network queries MUST specify explicit staleTime defaults and exponential backoff retry policies (e.g. max 2 retries, zero retries on 4xx client errors). Unhandled query errors MUST trigger localized error fallback boundaries rather than crashing the full UI tree.
  • [ ] Error Boundary Telemetry: Errors caught by error boundaries MUST be reported to a centralized observability service (e.g. Sentry, Datadog, or equivalent) before rendering the fallback UI. Silent swallowing of boundary errors or console.error-only logging is strictly forbidden in production.
  • [ ] URL as Authoritative State for Shareable UI: Shareable UI state — search filters, pagination cursors, active tabs, sort order — SHOULD be encoded in URL search parameters (?page=2&filter=active) rather than component useState or global stores. This ensures deep-linking, browser back/forward navigation, and SSR hydration correctness.
  • [ ] Schema-Driven Form Validation: User form inputs MUST be validated against explicit, type-safe schemas (e.g. Zod, Valibot, Yup) at the UI boundary before dispatching mutations, rendering co-located field-level errors linked via aria-describedby and aria-invalid.

3. Web Performance & Core Web Vitals ​

  • [ ] Core Web Vitals Thresholds: Production pages MUST maintain strict Core Web Vitals budgets on 75th percentile mobile runs: Largest Contentful Paint (LCP) <= 2.5s, Interaction to Next Paint (INP) <= 200ms, Cumulative Layout Shift (CLS) <= 0.1.
  • [ ] LCP Asset Prioritization: The likely LCP image asset MAY use fetchpriority="high" on the image element when measurement or page structure shows it benefits LCP. Use high priority sparingly, usually for only one or two likely LCP images. Critical above-the-fold and LCP candidate images MUST NOT use loading="lazy". Use <link rel="preload"> (with explicit imagesrcset/imagesizes for responsive images) only when the critical asset is discovered late via CSS or dynamic JavaScript rather than initial HTML.
  • [ ] Dynamic Code Splitting: All major route entries and heavy sub-components (e.g. rich text editors, data visualization charts, export modals) MUST be code-split using dynamic lazy loading (React.lazy / import()).
  • [ ] Zero Cumulative Layout Shift (CLS): Plain, unoptimized <img> tags without explicit dimensions are strictly forbidden. Images MUST use modern formats (WebP/AVIF) with explicit width and height attributes, container aspect-ratio reservations, and responsive srcset/sizes or <picture> element usage for multi-density displays.
  • [ ] Tree-Shaking & Import Hygiene: Barrel imports of monolithic utility or icon libraries (import * as Icons, importing from top-level lodash) are strictly banned. Use path-level tree-shakeable imports (import debounce from 'lodash/debounce').
  • [ ] Font & Asset Optimization: Web fonts MUST use font-display: swap or optional with preloaded critical self-hosted WOFF2 subsets (<link rel="preload" as="font" type="font/woff2" crossorigin>) to prevent Flash of Unstyled Text (FOUT) / Flash of Invisible Text (FOIT).

4. Security & Authentication Baseline ​

  • [ ] XSS Prevention & Link Sink Sanitization: Direct injection of unescaped HTML (dangerouslySetInnerHTML, v-html, innerHTML) is strictly banned unless explicitly sanitized using an audited library (e.g. DOMPurify). Dynamic URL bindings on navigation and media sinks (<a href>, <iframe>, window.location) MUST validate safe protocols (http:, https:, mailto:, relative paths) to block javascript: pseudo-protocol execution.
  • [ ] Secure Session & Token Storage: Storing sensitive access tokens, refresh tokens, session IDs, or user PII in localStorage or sessionStorage is strictly forbidden due to XSS exposure. Prefer Backend-for-Frontend (BFF) or same-site architectures using httpOnly, SameSite=Lax/Strict, Secure cookies with __Host- prefixes for host-scoped session isolation. Frontend API clients using cookie authentication MUST include anti-CSRF headers (e.g. X-CSRF-Token or custom headers paired with CORS) on state-changing mutations (POST, PUT, PATCH, DELETE). If browser-held access tokens are unavoidable, keep them encapsulated in private module scopes or Service Worker memory away from window global scope, keep lifetimes short, and never persist refresh tokens in browser storage.
  • [ ] Client Secret Hygiene & Environment Isolation: Hardcoded secrets, private API keys, and backend credentials in frontend source code or client bundles are strictly forbidden. Browser-accessible environment variables MUST use mandatory framework prefixes (e.g. NEXT_PUBLIC_, VITE_) and MUST NEVER expose private secrets or infrastructure credentials.
  • [ ] Open Redirect Safeguards: Client-side navigation routines (e.g. post-login redirects) MUST validate target URLs using the standard URL constructor against window.location.origin or an explicit trusted domain allowlist, explicitly rejecting protocol-relative URLs (//attacker.com) to prevent Open Redirect exploits.
  • [ ] Content Security Policy (CSP) & Defense-in-Depth Headers: Edge/server HTTP response headers MUST deliver strict CSP Level 3 constraints (restricting default-src 'self', disabling 'unsafe-eval'), restrict frame nesting (X-Frame-Options: DENY and CSP frame-ancestors 'none'), enforce HTTPS via Strict-Transport-Security (HSTS), prevent MIME sniffing (X-Content-Type-Options: nosniff), and enforce Referrer-Policy: strict-origin-when-cross-origin.
  • [ ] Subresource Integrity (SRI): Any third-party scripts or stylesheets loaded from external CDNs MUST include integrity and crossorigin attributes to guard against supply-chain compromise via CDN tampering.

5. Accessibility (a11y) & UX Invariants ​

  • [ ] WCAG 2.2 AA Compliance: All interactive UI components (buttons, forms, dialogs, dropdowns) MUST meet WCAG 2.2 AA as the non-negotiable baseline — minimum 4.5:1 text contrast ratio, 3:1 large text and UI component ratio.
  • [ ] Target Size Baseline (WCAG 2.2 SC 2.5.8): All pointer and touch interactive targets MUST meet a minimum bounding size of at least 24×24 CSS pixels (or provide 24px spacing between target centers/edges), targeting 44×44px or 48×48px for primary mobile touch targets.
  • [ ] Full Keyboard Navigation & Modal Focus Management: Every interactive element MUST be reachable and operable via keyboard alone (Tab, Enter, Space, Arrows). Modal dialogs and drawers MUST trap keyboard focus while open, dismiss on Escape, and restore focus to the invoking trigger element upon close. Composite widgets (tabs, menus, listboxes) MUST support standard arrow key navigation.
  • [ ] Visible Focus Indicators & Focus Visibility: CSS outline: none / outline: 0 on interactive elements without an explicit :focus-visible replacement is strictly banned. Custom focus rings MUST have at least 3:1 contrast against adjacent backgrounds (WCAG SC 1.4.11) and MUST NOT be obscured by sticky headers/footers or modal overlays (WCAG 2.2 SC 2.4.11). Positive tabindex values (tabindex > 0) are forbidden.
  • [ ] Semantic HTML & Accessible Naming: Native semantic HTML elements (<button>, <nav>, <main>, <dialog>) MUST be preferred over generic <div> or <span> with ARIA roles. All interactive controls (especially icon-only buttons and image links) MUST have an explicit accessible name via visible text, aria-label, aria-labelledby, or visually hidden text (.sr-only). aria-* attributes must not contradict underlying HTML semantics.
  • [ ] Dynamic Feedback & Live Region Hygiene: Asynchronous updates, loading completions, and non-blocking notifications MUST use aria-live="polite" (or role="status"). Reserve role="alert" (aria-live="assertive") strictly for time-critical, destructive errors. Set aria-busy="true" on async containers during active loading. Live region containers SHOULD be mounted in the DOM prior to dynamic content insertion. Inline form errors MUST be explicitly linked to form controls via aria-invalid="true" and aria-describedby="[error-id]".

6. Testing Strategy & QA ​

  • [ ] Testing Pyramid: Prefer a high proportion of fast unit tests for pure business logic and custom hooks, meaningful component interaction tests via Testing Library, and lightweight E2E smoke tests for critical user flows. Treat ratios such as 60% unit / 30% component / 10% E2E as targets, not mechanical quotas.
  • [ ] Async Act Hygiene & State Synchronization: All async component state updates and user interactions in tests MUST be awaited directly using userEvent or Testing Library async queries (waitFor, findBy*). Never wrap userEvent calls inside waitFor(), and never manually wrap RTL helpers in act(). Suppressing React act() warnings with console overrides or manual mocks is strictly banned.
  • [ ] Accessibility in Component Tests: New or changed interactive components MUST include user-outcome assertions with role-based queries (getByRole / findByRole) where applicable. Avoid low-value existence-only assertions and getByTestId as the primary selector when accessible roles or labels are available.
  • [ ] Automated & Manual Accessibility Assertions: New or changed interactive components and critical page states MUST run automated accessibility checks with axe-core or an equivalent tool (e.g. jest-axe / vitest-axe: expect(await axe(container)).toHaveNoViolations()). Because automation cannot catch all WCAG issues, critical flows MUST also receive manual keyboard testing and screen-reader spot checks where risk warrants it.
  • [ ] E2E Smoke Coverage: At minimum, the following critical user flows MUST have E2E test coverage: auth (login/logout), primary create/read/update/delete flow, and error boundary rendering.
  • [ ] Deterministic & Isolated Tests: Tests MUST NOT depend on real network calls. Network requests MUST be intercepted using MSW (Mock Service Worker) or equivalent, with runtime overrides reset (server.resetHandlers()) and onUnhandledRequest: 'error' configured to catch missing mocks. No setTimeout for fake async timing — use fake timers and restore real timers during teardown (vi.useRealTimers()).
  • [ ] Visual Regression Testing: Projects maintaining a shared component library or design system SHOULD run visual regression snapshots (e.g. Chromatic, Percy, or Playwright toHaveScreenshot()) on every PR touching design tokens or shared components. Pixel-diff thresholds MUST be committed to version control and reviewed as part of the PR.

7. Code Quality, CSS Maintainability & Design Tokens ​

  • [ ] Design Token Baseline: Product styling values (colors, spacing, typography scales, border radii, shadows) MUST be defined as CSS custom properties (--token-name) following a structured semantic hierarchy (e.g. --color-surface-primary, --spacing-md) in a shared design token file. Hardcoded magic hex values or repeated arbitrary spacing values in component styles are strictly forbidden. Literal values are allowed for technical one-offs such as 1px borders, media-query breakpoints, SVG/canvas coordinates, or browser-normalization fixes when their intent is clear.
  • [ ] Predictable CSS Architecture: Style rules MUST be co-located with their component (CSS Modules, styled-components, or utility-first scoped classes). Global CSS MUST be restricted to resets, token definitions, and base typography — no component-specific rules in global stylesheets.
  • [ ] Guard Clauses & Flat Render Logic: Conditional rendering MUST use early-return guard patterns at the top of component functions. Deeply nested ternary chains (a ? b ? c : d : e) inside JSX/templates are strictly banned.
  • [ ] Pragmatic DRY & YAGNI: Duplicate UI logic that appears three or more times MUST be extracted into a shared hook or utility. Speculative "reusable" abstractions with no current consumer MUST NOT be created.
  • [ ] Intent-Based Comments: Comments MUST explain non-obvious business rationale (why), never restating what readable code already expresses. Self-documenting component and hook names are required (useCartItemDiscountCalculator over useCalc).