Cursor Rules — Next.js App Router (RSC-First)
Project rules for Next.js App Router codebases: server-components-first, server actions with validation, the caching gotchas spelled out, and Tailwind + shadcn conventions.
# Project rules — Next.js (App Router)
## Server-first architecture
- Default to React Server Components. Add `'use client'` only for actual interactivity (event handlers, hooks, browser APIs) — and push the boundary down: a client leaf inside a server tree, never a client page with server islands.
- Data fetching happens in Server Components or Route Handlers, never in client `useEffect`. Client-side server state, where genuinely needed (polling, optimistic UI), uses TanStack Query.
- Mark server-only modules with `import 'server-only'` when they touch secrets or the DB — make the leak a build error, not an incident.
## Server Actions
- Every Server Action validates its input with zod at the top — form data is untrusted network input, and "the form only sends valid data" is not a security model.
- Every action re-checks authorization inside the action body. Middleware and UI hiding are not authorization.
- Return typed results (`{ ok: true, data } | { ok: false, error }`), never throw for expected failures. Call `revalidatePath`/`revalidateTag` explicitly after mutations.
## Caching — assume nothing
- Be explicit on every `fetch`: `{ cache: 'no-store' }` or `{ next: { revalidate: N, tags: [...] } }`. Never rely on remembered default behavior — it has changed between Next versions; the explicit option is self-documenting.
- Route segments that must be dynamic declare `export const dynamic = 'force-dynamic'` rather than depending on incidental dynamic APIs.
- Add `loading.tsx` and `error.tsx` per route segment that fetches; `error.tsx` must offer recovery (`reset()`), not just apologize.
## Components & styling
- Tailwind for styling; shadcn/ui for primitives — extend by composition in `components/ui`, never edit generated primitives in place.
- Props interfaces named `<Component>Props`, exported. Named exports everywhere; `default` only where the framework requires it (pages, layouts, route handlers).
- `next/image` for all raster images with real `width`/`height` or `fill`; `next/link` for all internal navigation. No raw `<img>`, no `<a>` to internal routes.
## Files & boundaries
- Feature folders: `app/(group)/feature/` owns its `page.tsx`, `actions.ts`, `components/`. Shared code graduates to `src/lib` or `src/components` only when a second feature imports it.
- Environment access only through a zod-validated `src/env.ts`; `NEXT_PUBLIC_` prefix is a deliberate publication decision, reviewed like one.
## When editing
- Never add `'use client'` to fix an error you have not understood — it usually masks a server/client boundary mistake.
- Match the segment's existing data-fetching pattern before introducing a new one.
Comments (0)