React / Next.js Code Generation Rules
Loaded when: package.json contains react or next.
These rules are ADDITIVE to the universal rules in SKILL.md.
Precedence: If preferred_libraries in design-tokens.json specifies a library not listed in the tables below, that selection takes priority. Use that library's standard API/import pattern. These tables cover common libraries but are not exhaustive. design-tokens.json is always authoritative for library selection.
Project conventions override: If design-tokens.json contains a conventions section, those conventions take priority over the canonical component template and patterns in this file for stylistic decisions (declaration style, export style, type definitions, file naming, import ordering, props pattern). This file remains authoritative for framework-specific syntax requirements (JSX, hooks, "use client", className).
0. NON-NEGOTIABLES
These React/Next.js rules hold for every generated file. No exceptions.
- MUST use
className, NEVERclass. JSX does not accept the HTMLclassattribute. - NEVER use
useEffectfor data fetching. Use the library selected inpreferred_libraries.data_fetching.selected(React Query, SWR, etc.). Fetching insideuseEffectcauses waterfalls and bypasses the selected data layer. - NEVER mark a component
"use client"unless it actually uses client hooks or browser APIs. Server Components are the default in App Router; opting in unnecessarily ships extra JavaScript and breaks server-only imports. - MUST provide a stable
keyon every.map()-produced element. NEVER use the array index as the key when the list can reorder. - MUST import React hooks from
"react"directly. NEVER alias or re-export them through a local barrel in a way that hides the origin.
1. KEY REMINDERS
- Add
"use client"ONLY when a component uses hooks, events, or browser APIs. All other components are Server Components by default in App Router. - React 19+:
useActionStateis fromreact(NOTreact-dom).use()can be conditional.<form action={serverAction}>passes async fn directly. - Always use
className, neverclass. Always providekeyon.map()elements.
2. SYNTAX QUICK REFERENCE (framework-specific gotchas and newer APIs only)
| Concept | React / Next.js Syntax |
|---|---|
| Form action | <form action={serverAction}> — pass async fn directly (React 19+) |
| Form state | const [state, action, pending] = useActionState(fn, init) (from react, NOT react-dom) |
| Form status | const { pending } = useFormStatus() (from react-dom, inside <form>) |
| Async read | const value = use(promise) or const ctx = use(MyContext) (can be conditional, React 19+) |
3. FILE STRUCTURE CONVENTIONS
| Type | Path |
|---|---|
| Page (App Router) | app/[route]/page.tsx |
| Page (Pages Router) | pages/[name].tsx |
| Layout | app/layout.tsx, app/[route]/layout.tsx |
| Reusable component | src/components/ui/ComponentName.tsx |
| Page-specific comp | app/[route]/_components/Name.tsx |
| Custom hook | src/hooks/useHookName.ts |
| API route handler | app/api/[route]/route.ts |
| Service / API lib | src/services/ or src/lib/api/ |
| Types | src/types/ or co-located types.ts |
4. DATA FETCHING PATTERNS
| Context | Pattern |
|---|---|
| Server Component | async function Page() { const data = await fetch(url); } |
| Client + React Query | const { data } = useQuery({ queryKey, queryFn }) |
| Client + SWR | const { data } = useSWR(key, fetcher) |
| Server Action | "use server" async function in separate file or inline |
| Route Handler | export async function GET(req: NextRequest) { ... } |
Next.js 15 breaking change: params and searchParams in page.tsx / layout.tsx are now Promise objects. Always await them:
export default async function Page(props: { params: Promise<{ slug: string }> }) {
const { slug } = await props.params;
}
5. CLIENT / SERVER BOUNDARIES
Add "use client" at the top of a file if and ONLY if it uses any of:
useState,useEffect,useReducer,useContext,useRefwith DOM access,useActionState,useFormStatus,useOptimistic- Event handlers (
onClick,onChange,onSubmit, etc.) - Browser APIs (
window,document,localStorage,IntersectionObserver) - Third-party hooks that require client context (
useQuery,useForm,useRouterfromnext/navigation)
All other components are Server Components by default in App Router. Pages Router does not use this directive.
6. STYLING SYNTAX
| Method | Syntax |
|---|---|
| Tailwind | className="flex items-center gap-2 text-sm" |
| Tailwind + cn | className={cn("base-class", isActive && "bg-primary")} |
| CSS Modules | import s from './Name.module.css'; className={s.wrapper} |
7. FRAMEWORK-SPECIFIC LIBRARY CATEGORIES
| Category | React Libraries |
|---|---|
| data_fetching | @tanstack/react-query, swr, axios |
| forms | react-hook-form, formik |
| icons | lucide-react, react-icons, @heroicons/react |
| animation | framer-motion, react-spring |
| component_library | @radix-ui/themes, @shadcn/ui, @mui/material, @mantine/core, @chakra-ui/react |
| tables | @tanstack/react-table |
| charts | recharts, @nivo/core, victory |
| date_picker | react-datepicker, react-day-picker |
| toast | sonner, react-hot-toast, react-toastify |
| dnd | @dnd-kit/core, react-beautiful-dnd |
Rule: check package.json before importing. If a library from this table is already installed, use it. Never add a second library for the same category.
Library Boundary Values (SVG Chart Libraries)
Some libraries accept color/style values as string props, not CSS classes or variables. SVG-based chart libraries (recharts, @nivo/core, victory, d3) are the most common case — their fill, stroke, and color props require hex/rgb strings because the SVG renderer does not resolve CSS custom properties at the attribute level.
Pattern: Resolve CSS variables at runtime with useMemo.
Instead of hardcoding hex values directly, resolve design tokens from CSS variables so the single source of truth remains in your design system:
"use client";
import { useMemo } from "react";
import { BarChart, Bar } from "recharts";
function resolveTokenColor(tokenVar: string): string {
if (typeof window === "undefined") return "#000000";
return getComputedStyle(document.documentElement)
.getPropertyValue(tokenVar)
.trim() || "#000000";
}
function MyChart({ data }: { data: DataPoint[] }) {
const colors = useMemo(() => ({
primary: resolveTokenColor("--colors-primary"),
muted: resolveTokenColor("--colors-muted-foreground"),
border: resolveTokenColor("--colors-border"),
}), []);
return (
<BarChart data={data}>
{/* Token: colors.primary — resolved at runtime for SVG compatibility */}
<Bar dataKey="value" fill={colors.primary} />
</BarChart>
);
}
When getComputedStyle is not feasible (SSR-only, data arrays, or when the token variable name is not known), hardcoding is acceptable but MUST include a comment linking the value to its token:
// Token: colors.primary (#2563EB) — hardcoded for recharts SVG compatibility
<Bar dataKey="value" fill="#2563EB" />
These values are exempt from the Phase 5 hardcoded-values audit (bucket A) because the library API requires them. The Phase 5 audit should skip values that appear inside SVG chart library props (fill, stroke, color on recharts/nivo/victory/d3 components) when they carry the // Token: comment.