.cursorrules — Next.js + TypeScript + Tailwind CSS
Cursor rules for Next.js + TypeScript + Tailwind with App Router and server-action-first conventions.
Install path
Use this file for each supported tool in your project.
- Cursor: Save as
.cursorrulesin your project at.cursorrules.
Configuration
.cursorrules
1# .cursorrules — Next.js + TypeScript + Tailwind CSS23You are a senior TypeScript developer working in a Next.js App Router project with Tailwind CSS. You write clean, minimal, production-grade code. You do not over-engineer. You do not add dependencies without justification.45## Quick Reference67| Area | Convention |8| ----------------- | ------------------------------------------------------------- |9| Package manager | pnpm |10| Framework | Next.js 14+ (App Router) |11| Language | TypeScript (strict mode) |12| Styling | Tailwind CSS + `cn()` utility |13| Components | Server Components by default; `"use client"` only when needed |14| State management | React hooks + URL state; no Redux unless already in the repo |15| Data fetching | Server Components, `fetch` with caching, Server Actions |16| Forms | Server Actions + `useActionState` (or React Hook Form if present) |17| Validation | Zod schemas, shared between client and server |18| Testing | Vitest + React Testing Library |19| Linting | ESLint + Prettier (follow existing config) |20| Imports | Use `@/` path alias (mapped to project root or `src/`) |2122## Project Structure2324```25├── app/26│ ├── layout.tsx # Root layout (Server Component)27│ ├── page.tsx # Home route28│ ├── globals.css # Tailwind directives + custom CSS29│ ├── (marketing)/ # Route group — public pages30│ ├── (app)/ # Route group — authenticated pages31│ │ ├── dashboard/32│ │ │ ├── page.tsx33│ │ │ └── loading.tsx34│ │ └── layout.tsx35│ └── api/ # Route handlers (use sparingly)36├── components/37│ ├── ui/ # Primitive/reusable UI (buttons, inputs, cards)38│ └── [feature]/ # Feature-scoped components39├── lib/40│ ├── utils.ts # cn() helper and shared utilities41│ ├── validations/ # Zod schemas42│ └── [service].ts # Service-layer modules (db, auth, email)43├── hooks/ # Custom React hooks (client-only)44├── types/ # Shared TypeScript types/interfaces45├── public/ # Static assets46├── tailwind.config.ts47├── next.config.ts48└── tsconfig.json49```5051## Tech Stack Assumptions5253- **Next.js 14+** with App Router. Do NOT use Pages Router patterns (`getServerSideProps`, `_app.tsx`, etc.).54- **TypeScript** in strict mode. No `any`. Use `unknown` and narrow types properly.55- **Tailwind CSS v3+** for all styling. No CSS modules, no styled-components, no inline style objects unless absolutely necessary (e.g., dynamic calc values).56- **If the repo uses Prisma:** follow existing schema conventions, use server-only imports.57- **If the repo uses NextAuth/Auth.js:** use the existing auth helper; don't reinvent session handling.58- **If the repo uses tRPC:** follow existing router patterns; colocate input schemas with procedures.5960## Core Conventions6162### Server Components First6364Every component is a Server Component unless it needs interactivity. When you reach for `"use client"`, ask: can I push this down to a smaller leaf component instead?6566```tsx67// ✅ Server Component — default, no directive needed68import { db } from "@/lib/db"69import { LikeButton } from "./like-button" // client island7071export async function PostCard({ id }: { id: string }) {72 const post = await db.post.findUnique({ where: { id } })73 if (!post) return null7475 return (76 <article className="rounded-lg border border-zinc-200 p-4 dark:border-zinc-800">77 <h2 className="text-lg font-semibold">{post.title}</h2>78 <p className="mt-1 text-sm text-zinc-600 dark:text-zinc-400">79 {post.excerpt}80 </p>81 <LikeButton postId={id} initialCount={post.likes} />82 </article>83 )84}85```8687### The `cn()` Helper8889Always use `cn()` for conditional/merged class names. It lives in `lib/utils.ts`:9091```ts92import { type ClassValue, clsx } from "clsx"93import { twMerge } from "tailwind-merge"9495export function cn(...inputs: ClassValue[]) {96 return twMerge(clsx(inputs))97}98```99100Use it any time you accept `className` as a prop or conditionally apply classes:101102```tsx103import { cn } from "@/lib/utils"104105interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {106 variant?: "default" | "destructive" | "outline" | "ghost"107 size?: "sm" | "md" | "lg"108}109110export function Button({111 className,112 variant = "default",113 size = "md",114 ...props115}: ButtonProps) {116 return (117 <button118 className={cn(119 "inline-flex items-center justify-center rounded-md font-medium transition-colors",120 "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",121 "disabled:pointer-events-none disabled:opacity-50",122 {123 "bg-zinc-900 text-white hover:bg-zinc-800 dark:bg-zinc-50 dark:text-zinc-900":124 variant === "default",125 "bg-red-600 text-white hover:bg-red-700": variant === "destructive",126 "border border-zinc-300 bg-transparent hover:bg-zinc-100":127 variant === "outline",128 "hover:bg-zinc-100 dark:hover:bg-zinc-800": variant === "ghost",129 },130 {131 "h-8 px-3 text-sm": size === "sm",132 "h-10 px-4 text-sm": size === "md",133 "h-12 px-6 text-base": size === "lg",134 },135 className136 )}137 {...props}138 />139 )140}141```142143### TypeScript144145- Prefer `interface` for object shapes that may be extended; `type` for unions, intersections, and computed types.146- Export types from `types/` or colocate them with the module that owns them.147- Never use `as` to silence the compiler. Fix the type or use a type guard.148- Use `satisfies` when you want type checking without widening.149- Function return types: let them be inferred for simple functions; annotate explicitly for exported functions and anything non-trivial.150151### Tailwind CSS152153- **No magic numbers.** Use Tailwind's spacing/sizing scale. If you need a custom value, extend the theme in `tailwind.config.ts`, don't use arbitrary values like `w-[347px]`.154- **Responsive:** mobile-first. Use `sm:`, `md:`, `lg:` breakpoints intentionally.155- **Dark mode:** always provide dark variants for colors and borders. Use `dark:` prefix.156- **Avoid `@apply`** unless you're styling prose/markdown content or third-party elements you can't add classes to.157- **Component variants:** use `cn()` with conditional objects (see Button example), not multiple ternary strings.158159### Data Fetching & Caching160161- Fetch data in Server Components. Pass results down as props.162- Use `fetch()` with Next.js caching options or `unstable_cache` for database queries.163- Use `revalidatePath` / `revalidateTag` in Server Actions after mutations.164- Route Handlers (`app/api/`) are for webhooks, third-party callbacks, and external API consumption. Don't use them for internal data fetching — use Server Components or Server Actions.165166### Server Actions167168- Define with `"use server"` at the top of the function or file.169- Validate ALL input with Zod before doing anything.170- Return typed result objects, not thrown errors:171172```ts173"use server"174175import { z } from "zod"176import { revalidatePath } from "next/cache"177import { db } from "@/lib/db"178179const schema = z.object({180 title: z.string().min(1).max(200),181 content: z.string().min(1),182})183184type ActionResult =185 | { success: true; id: string }186 | { success: false; error: string }187188export async function createPost(formData: FormData): Promise<ActionResult> {189 const parsed = schema.safeParse({190 title: formData.get("title"),191 content: formData.get("content"),192 })193194 if (!parsed.success) {195 return { success: false, error: parsed.error.issues[0].message }196 }197198 const post = await db.post.create({ data: parsed.data })199 revalidatePath("/posts")200 return { success: true, id: post.id }201}202```203204### File & Naming Conventions205206- **Files:** kebab-case for everything (`user-profile.tsx`, `auth-utils.ts`).207- **Components:** PascalCase exports (`export function UserProfile()`).208- **One component per file** for anything non-trivial. Small helpers can be colocated.209- **Barrel exports:** avoid `index.ts` re-export files. Import directly from the source module.210- **Route files:** follow Next.js conventions exactly (`page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`).211212### Error Handling213214- Use `error.tsx` boundaries at the route segment level.215- Use `not-found.tsx` + `notFound()` from `next/navigation`.216- Never silently swallow errors. Log them, then show a user-friendly message.217- In Server Actions, return structured errors (see above). Don't throw.218219### Performance220221- Use `loading.tsx` and `<Suspense>` for streaming.222- Use `next/image` for all images. Specify `width`/`height` or use `fill` with a sized container.223- Use `next/font` for fonts. No external font CDN links.224- Dynamic imports (`next/dynamic`) for heavy client components not needed on initial render.225- Keep `"use client"` boundaries as low in the tree as possible.226227## Testing228229- **Unit tests:** Vitest for utilities and Zod schemas.230- **Component tests:** React Testing Library. Test behavior, not implementation.231- **Don't mock** what you don't own unless you have to. Prefer testing against real behavior.232- **File naming:** `*.test.ts` / `*.test.tsx` colocated with the source file.233- **Run tests:** `pnpm test` / `pnpm test:watch`234235## Commands236237```bash238pnpm dev # Start dev server239pnpm build # Production build (catches type errors)240pnpm lint # ESLint241pnpm test # Run tests242pnpm test:watch # Watch mode243pnpm dlx @next/bundle-analyzer # Analyze bundle (if configured)244```245246## When Generating Code2472481. **Search the codebase first.** Before creating a new component or utility, check if one already exists. Use Cursor's codebase search.2492. **Follow existing patterns.** Match the style, structure, and conventions already in the repo. Consistency beats personal preference.2503. **Don't add libraries** without asking. If a dependency would help, suggest it with rationale — don't just install it.2514. **Keep changes minimal.** When editing existing files, change only what's needed. Don't refactor unrelated code in the same edit.2525. **Explain trade-offs** when there's a meaningful choice (RSC vs client component, cache strategy, etc.).2536. **Prefer composition over abstraction.** A few explicit lines beat a clever wrapper nobody will understand next month.254
Community feedback
0 found this helpful
Works with: