Cursor Rules Tutorial — How to Write a .cursorrules File That Actually Controls Claude Behavior
Most .cursorrules files don't work. Not because the format is wrong — because the rules are vague, unstructured, and give the AI too much room to interpret.
I've written and tested over 50 cursor rules across 20+ production projects. This tutorial walks you through writing a .cursorrules file that actually controls how Claude behaves inside Cursor, step by step.
What Is a .cursorrules File?
A .cursorrules file sits in the root of your project. When Cursor reads it, it prepends your rules to every prompt sent to the underlying AI model (Claude, GPT-4, etc.). Think of it as a system prompt for your codebase.
The file is plain text. No special syntax required. But structure matters — a lot.
# .cursorrules
# Place this file in the root of your project
You are an expert TypeScript developer working on a Next.js SaaS application.
Follow these rules strictly when generating or modifying code.
That preamble sets the identity. Everything after it defines the boundaries.
Step 1: Define the Stack Explicitly
The single most impactful rule you can write is a clear stack definition. Without it, Claude guesses — and guesses wrong.
Bad:
Use modern JavaScript best practices.
Good:
# === STACK ===
- Language: TypeScript 5.x with strict mode enabled
- Framework: Next.js 14 (App Router, NOT Pages Router)
- Database: PostgreSQL via Prisma ORM
- Auth: NextAuth.js v5
- Styling: Tailwind CSS (no inline styles, no CSS modules)
- State: React Server Components by default, client components only when needed
This eliminates an entire category of mistakes. Claude won't suggest Pages Router patterns, won't use CSS modules, and won't wrap everything in "use client" when server components work fine.
Step 2: Set Negative Constraints First
Telling Claude what NOT to do is more effective than telling it what to do. Negative constraints create hard boundaries. Positive instructions are suggestions.
# === NEVER DO THIS ===
- Never use `any` type. Use `unknown` if the type is truly unknown.
- Never use default exports except for Next.js page/layout components.
- Never use `useEffect` for data fetching. Use server components or React Query.
- Never add TODO comments. Implement it now or explain what's blocking you.
- Never use `console.log` in production code. Use the project logger.
- Never swallow errors silently. Always handle or re-throw with context.
Why negatives first? Because Claude models tend to be eager to generate code. When they hit a negative constraint, they stop and reconsider. When they hit a positive instruction, they treat it as one option among many.
Step 3: Organize Rules by Priority
When rules conflict, Claude picks one — and you can't predict which. Explicit priority sections fix this.
# === CRITICAL (always enforced) ===
- All API routes must validate input with Zod before processing.
- All database mutations must be wrapped in try/catch with proper error responses.
- Never expose internal error messages to the client.
# === HIGH PRIORITY (enforced unless it conflicts with Critical) ===
- Prefer server components over client components.
- Use TypeScript interfaces for API response shapes.
- Extract reusable logic into /lib, not into component files.
# === PREFERENCES (follow when possible) ===
- Prefer named functions over arrow functions for top-level declarations.
- Use early returns to reduce nesting.
- Keep functions under 30 lines when practical.
The section headers aren't magic — Claude reads them as natural language and respects the hierarchy. The key is making the hierarchy explicit instead of hoping the AI infers importance from ordering alone.
Step 4: Add Framework-Specific Scoping
Generic rules bleed into the wrong context. A rule like "always use named exports" breaks Next.js pages. Scope your rules to where they apply.
# === REACT COMPONENTS ===
- Use default exports only for page.tsx, layout.tsx, and loading.tsx files.
- Colocate component-specific types in the same file.
- Props interfaces must be named [ComponentName]Props.
# === API ROUTES ===
- Every route handler must return typed responses using NextResponse.json().
- Validate all request bodies with Zod schemas defined in /lib/validations.
- Use HTTP status codes correctly: 400 for bad input, 401 for unauthed, 403 for forbidden.
# === DATABASE / PRISMA ===
- Never use raw SQL unless Prisma can't express the query.
- Always select only the fields you need — no implicit select-all.
- Wrap related mutations in $transaction blocks.
Scoped rules prevent one context from polluting another. Claude can tell whether it's writing a React component or an API route and applies only the relevant section.
Step 5: Include Code Examples for Complex Patterns
For patterns that are hard to describe in words, show Claude exactly what you want.
# === ERROR HANDLING PATTERN ===
# Always follow this pattern for API route error handling:
# Example:
export async function POST(request: Request) {
try {
const body = await request.json();
const validated = createUserSchema.parse(body);
const user = await prisma.user.create({ data: validated });
return NextResponse.json({ success: true, data: user }, { status: 201 });
} catch (error) {
if (error instanceof ZodError) {
return NextResponse.json(
{ success: false, error: error.errors },
{ status: 400 }
);
}
console.error('[POST /api/users]', error);
return NextResponse.json(
{ success: false, error: 'Internal server error' },
{ status: 500 }
);
}
}
One concrete example is worth ten abstract rules. Claude pattern-matches against examples more reliably than it interprets prose descriptions.
Step 6: Handle Deprecated Patterns
Your codebase has old patterns. Claude will learn from them unless you say otherwise.
# === DEPRECATED — DO NOT USE ===
- Do not use /pages directory (migrated to App Router)
- Do not use getServerSideProps or getStaticProps (use server components)
- Do not use next/router (use next/navigation)
- Do not use the old Prisma $queryRaw for pagination (use cursor-based pagination)
This is critical. Claude reads your existing codebase for context. If old patterns exist in your code, Claude will replicate them unless your rules explicitly mark them as deprecated.
The Complete Template
Here's the full structure:
# .cursorrules
You are an expert [LANGUAGE] developer working on a [FRAMEWORK] application.
# === STACK ===
[Your stack definition]
# === NEVER DO THIS ===
[Hard constraints]
# === CRITICAL RULES ===
[Non-negotiable requirements]
# === HIGH PRIORITY ===
[Important patterns]
# === [FRAMEWORK] SPECIFIC ===
[Scoped rules per context]
# === CODE PATTERNS ===
[Examples of correct implementations]
# === DEPRECATED ===
[Patterns to avoid]
# === PREFERENCES ===
[Nice-to-haves]
.cursorrules vs CLAUDE.md
If you're using Claude Code (the terminal-based agent), the equivalent file is CLAUDE.md. It serves the same purpose — system-level instructions — but lives in the Claude Code ecosystem. The rules translate directly: same structure, same patterns, same priority system.
The difference is scope. .cursorrules controls Cursor IDE behavior. CLAUDE.md controls Claude Code behavior. If you use both tools, you want both files.
Stop Guessing, Start Controlling
A well-structured .cursorrules file turns Cursor from a coin-flip code generator into a predictable development partner. The key principles:
- Be explicit — define your stack, don't let the AI guess
- Negatives first — hard constraints before soft preferences
- Prioritize — section headers create hierarchy
- Scope — rules apply to specific contexts
- Show, don't tell — code examples beat prose descriptions
- Deprecate — block old patterns explicitly
If you want a ready-made .cursorrules file with 50 production-tested rules for TypeScript/Next.js/React projects, check out the Cursor Rules Pack v2 — it's structured exactly like this tutorial describes.
And if you're using Claude Code, the CLAUDE.md Rules Pack gives you the same coverage for terminal-based AI development.
Both are drop-in files. No configuration needed. Just add to your project root and start building.
Top comments (0)