How to Write Cursor Rules: Best Practices for AI-Powered Coding

How to Write Cursor Rules: Best Practices for AI-Powered Coding
Your AI coding assistant doesn't know your team prefers functional components over class components. It doesn't know you use Zod for validation, Drizzle for ORM, or that every API endpoint must return a standardized error format. It writes perfectly valid code that violates every convention your team spent two years establishing.
Cursor rules files fix this. A single `.cursorrules` file in your project root transforms Cursor from a generic code generator into one that follows your team's patterns. The difference between "AI-generated code that needs 20 minutes of cleanup" and "AI-generated code that passes code review" is almost always a rules file.
What .cursorrules Files Do
A `.cursorrules` file is a plain-text instruction set that Cursor injects into every AI prompt for your project. It's system-prompt engineering for your codebase — persistent instructions that shape how the AI writes code, structures files, handles errors, and follows conventions.
Every time you make a request in Cursor — Chat, Composer, or Agent Mode — the contents of `.cursorrules` are prepended to your prompt. The model reads your rules before it reads your request, which means the rules act as a behavioral filter on all generated output.
Key properties:
- Format: Plain text or Markdown (Markdown is recommended for readability)
- Location: Project root (`.cursorrules`) or `.cursor/rules/` directory for granular rules
- Scope: Applied to all Cursor interactions within the project
- Size: Typically 500-2,000 words — long enough to cover conventions, short enough to not waste context
- Priority: Rules are processed before your prompt, giving them high influence on output
The rules file is committed to git. Every team member gets the same AI behavior. No more "it works differently on my machine" — at least for AI-generated code.
File Format and Location
Cursor supports two approaches to rules, and using both simultaneously is the most powerful setup.
Project-Level Rules (.cursorrules)
Place a `.cursorrules` file in your project root. This file applies to all AI interactions across the entire project.
```
my-project/
├── .cursorrules ← Global project rules
├── src/
├── package.json
└── ...
```
This is the simplest approach and covers 80% of use cases. Start here.
Granular Rules (.cursor/rules/)
For larger projects, you can create multiple rule files in `.cursor/rules/`, each scoped to specific file patterns or directories. Each file contains a frontmatter section defining its scope and a body with the rules.
```
my-project/
├── .cursor/
│ └── rules/
│ ├── react-components.mdc ← Rules for *.tsx files
│ ├── api-routes.mdc ← Rules for src/api/**
│ ├── testing.mdc ← Rules for *.test.ts files
│ └── database.mdc ← Rules for src/db/**
├── src/
└── ...
```
Each `.mdc` file uses frontmatter to specify when it activates:
```markdown
---
description: React component conventions
globs: ["**/*.tsx", "**/*.jsx"]
alwaysApply: false
---
- Use functional components with arrow functions
- Props interface named {ComponentName}Props
- Export components as named exports, not default
```
Granular rules activate only when you're working on matching files, which saves context window space. API route rules don't load when you're editing React components, and vice versa.
What to Include in Your Rules
The best rules files share three qualities: they're specific (actionable patterns, not vague principles), they're stable (conventions that won't change weekly), and they're distinctive (things the AI wouldn't do by default).
Coding Conventions
```markdown
Code Style
- Use `const` for all variable declarations; never use `let` unless mutation is required
- Prefer named exports over default exports
- Use explicit return types on all public functions
- Maximum function length: 30 lines — extract helper functions beyond that
- Use template literals for string concatenation, never `+`
```
Framework Preferences
```markdown
Framework Patterns
- State management: Zustand (not Redux, not Context API for shared state)
- Forms: React Hook Form + Zod validation schemas
- API calls: TanStack Query (useQuery/useMutation), never raw fetch in components
- Routing: Next.js App Router — use server components by default
- Styling: Tailwind CSS utility classes — no CSS modules, no styled-components
```
Error Handling Patterns
```markdown
Error Handling
- All API endpoints must use the AppError class from src/lib/errors.ts
- Never throw raw Error objects — always use typed error codes
- API responses follow: { success: boolean, data?: T, error?: { code: string, message: string } }
- Database operations must be wrapped in try/catch with specific error types
- Log errors with structured logging: logger.error({ err, context: { ... } })
```
Testing Patterns
```markdown
Testing
- Test files: colocated with source files as `{filename}.test.ts`
- Testing framework: Vitest (not Jest)
- Use `describe` blocks grouped by method/function name
- Mock external services with MSW, never mock fetch directly
- Minimum coverage: every public function must have at least one happy-path and one error-path test
```
Architectural Decisions
```markdown
Architecture
- Repository pattern for all database access — no raw queries in services
- Services are stateless — inject dependencies through constructor
- DTOs for API boundaries — never expose database entities directly
- Use result types (Result<T, E>) instead of throwing for expected errors
- Feature modules: src/features/{name}/ with index.ts barrel export
```
What NOT to Include
Rules that are too detailed, too volatile, or too generic hurt more than they help.
Don't List Files or Directory Structures
```markdown
BAD — changes every time you add a file
The project structure is:
- src/api/users.ts
- src/api/payments.ts
- src/api/orders.ts
...
```
File listings go stale immediately and waste context tokens. The AI can read your actual file system — let it.
Don't Include Dependency Details
```markdown
BAD — changes every npm update
We use React 18.2.0, Next.js 14.1.2, TypeScript 5.3.3...
```
Version-specific instructions break with every update. Only mention dependencies when the instruction is version-dependent ("Use Next.js App Router, not Pages Router").
Don't Repeat Language Defaults
```markdown
BAD — the AI already knows this
Use TypeScript for type safety. Handle null values carefully.
Write clean, readable code.
```
These instructions are too generic to influence behavior. The AI already follows these principles. Rules should specify your deviations from the default, not restate the default.
Don't Include Long Code Examples
```markdown
BAD — wastes context window
Here's a complete example of how to write an API route:
[50 lines of code]
```
Rules consume context window space on every request. A 50-line example costs 300-500 tokens per interaction. Instead, reference the file: "Follow the pattern in `src/api/users/route.ts` for new API routes."
Don't Include Ephemeral Information
```markdown
BAD — needs constant updating
Current sprint focus: payment refactoring
Known bugs: #423, #456
```
Rules files should be updated quarterly, not daily. Put sprint-specific instructions in your prompt, not your rules.
Example Rules for Popular Frameworks
React + Next.js (App Router)
```markdown
Stack
- Next.js App Router with React Server Components
- TypeScript strict mode
- Tailwind CSS for styling
- Zustand for client state
- TanStack Query for server state
Components
- Server components by default; add 'use client' only when needed
- Functional components with arrow functions
- Props type: {ComponentName}Props (interface, not type alias)
- Colocate styles — no separate CSS files
Data Fetching
- Server components: async/await with direct database/API calls
- Client components: TanStack Query hooks
- Never use useEffect for data fetching
- API routes in src/app/api/ using Route Handlers
Error Handling
- Use error.tsx boundaries for route-level errors
- API routes return NextResponse.json with typed responses
- Use Zod for request validation in API routes
```
Python (FastAPI)
```markdown
Stack
- FastAPI with async endpoints
- SQLAlchemy 2.0 with async sessions
- Pydantic v2 for validation
- pytest for testing
Code Style
- Type hints on all function parameters and return values
- Use async/await for all I/O operations
- Dependency injection via FastAPI Depends()
- Pydantic models for all request/response schemas
Architecture
- src/{module}/ with router.py, service.py, models.py, schemas.py
- Business logic in service layer, never in routers
- Database access through repository classes
- Alembic for migrations — auto-generate, then review
```
Node.js (Express/Fastify)
```markdown
Stack
- Fastify (not Express) with TypeScript
- Prisma ORM with PostgreSQL
- Zod for validation
- Vitest for testing
Code Style
- Use ESM imports (not CommonJS require)
- Async route handlers — always await, never .then()
- Typed request/response schemas with Zod
- Structured logging with Pino
Architecture
- Controller → Service → Repository pattern
- Input validation in controller layer with Zod schemas
- Business logic in service layer
- Raw SQL only through Prisma.$queryRaw when ORM is insufficient
```
Rules vs Context: Two Different Problems
Rules and context solve fundamentally different problems, and confusing them is the most common mistake in AI-assisted development.
Rules tell the AI HOW to code. Coding conventions, framework preferences, error patterns, testing approaches. Rules shape the style and structure of generated code. They're static — they don't change from task to task.
Context tells the AI WHAT code exists. Function signatures, type definitions, dependency relationships, existing implementations. Context is dynamic — it changes with every task based on which parts of the codebase are relevant.
A rules file that says "use Zod for validation" is a rule. The actual Zod schema for the User model that the AI needs to reference when building a user endpoint — that's context.
The mistake: Developers try to put context in their rules file — listing every type, every function signature, every file path. This wastes context window space on every request (rules are injected every time) and goes stale immediately as code changes.
The fix: Rules file handles conventions. A context engine handles code awareness.
Comparing .cursorrules to CLAUDE.md
If you use Claude Code alongside or instead of Cursor, you've encountered the `CLAUDE.md` file — Claude Code's equivalent of `.cursorrules`. The concepts are similar, but the implementation differs.
Similarities:
- Both are project-root Markdown files committed to git
- Both inject instructions into every AI interaction
- Both shape coding style, conventions, and patterns
- Both are read by the team (human-readable documentation of conventions)
Differences:
- `.cursorrules` supports granular scoping via `.cursor/rules/*.mdc` with glob patterns. `CLAUDE.md` is a single file with no built-in scoping mechanism.
- `CLAUDE.md` can include tool usage instructions (e.g., "use run_pipeline for every task"). `.cursorrules` focuses on code output conventions.
- `CLAUDE.md` supports nested files (`CLAUDE.md` files in subdirectories) for monorepo structures. `.cursorrules` uses the `.cursor/rules/` directory pattern instead.
- Claude Code reads `CLAUDE.md` at the root, plus any `CLAUDE.md` in the current working directory. Cursor reads `.cursorrules` at the root plus any matching `.mdc` files.
If you use both tools, maintain both files with overlapping content for shared conventions. The 5-minute overhead of keeping them in sync is worth the consistency. Alternatively, have your `CLAUDE.md` reference the `.cursorrules` content with a note that conventions are shared.
How Context Engines Complement Rules Files
Rules tell the AI your conventions. Context tells it your code. Together, they produce output that is both stylistically correct and functionally accurate.
Without a context engine, the AI follows your rules but guesses at the existing code structure. It creates a new API endpoint following your error-handling pattern (good) but doesn't know that a helper function for the same logic already exists (wasteful). It writes a Zod schema matching your conventions (good) but duplicates a type definition that's already in your types file (bad).
A context engine like vexp bridges this gap. It serves the dependency graph — which functions exist, which types are defined, which modules depend on which — so the AI generates code that follows your rules AND integrates with your existing code.
Rules without context: Correct style, possible duplication, missed existing patterns.
Context without rules: Correct integration, inconsistent style, unknown conventions.
Rules + context: Correct style, correct integration, minimal cleanup. This is the setup that produces code that passes code review without modification.
The rules file takes 30 minutes to write once and 5 minutes to update per quarter. A context engine takes 2 minutes to install and indexes automatically. Combined, they transform AI-generated code from "needs heavy editing" to "ready to merge" — a difference measured in hours saved per week.
Frequently Asked Questions
Where do I put my Cursor rules file?
How long should a .cursorrules file be?
What's the difference between .cursorrules and CLAUDE.md?
Should I put file listings and dependency information in my rules file?
Do Cursor rules replace the need for a context engine?
Nicola
Developer and creator of vexp — a context engine for AI coding agents. I build tools that make AI coding assistants faster, cheaper, and actually useful on real codebases.
Related Articles

Vibe Coding Is Fun Until the Bill Arrives: Token Optimization Guide
Vibe coding with AI is addictive but expensive. Freestyle prompting without context management burns tokens 3-5x faster than structured workflows.

Code Indexing for AI Agents: Embeddings vs Dependency Graphs vs RAG
Three approaches to code indexing for AI: embeddings, dependency graphs, and RAG. Each has trade-offs in accuracy, token efficiency, and maintenance cost.

RAG for Code: Retrieval-Augmented Generation in AI Development
RAG retrieves relevant code from your codebase before the AI generates a response. But vector-based RAG misses structural relationships that matter for coding.