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

Nicola·
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.

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?
Place a `.cursorrules` file in your project root directory. This applies rules to all AI interactions across the project. For granular rules scoped to specific file types or directories, create `.mdc` files in the `.cursor/rules/` directory with frontmatter specifying glob patterns for when each rule file activates.
How long should a .cursorrules file be?
Aim for 500-2,000 words. Long enough to cover your key conventions — coding style, framework preferences, error handling, testing patterns, and architectural decisions — but short enough to not waste significant context window space. Rules are injected into every AI request, so every extra word costs tokens on every interaction.
What's the difference between .cursorrules and CLAUDE.md?
Both are project-root Markdown files that shape AI coding behavior. `.cursorrules` is for Cursor and supports granular scoping via `.cursor/rules/*.mdc` files with glob patterns. `CLAUDE.md` is for Claude Code and supports nested files in subdirectories for monorepo structures. If you use both tools, maintain both files with shared conventions to ensure consistent AI behavior across editors.
Should I put file listings and dependency information in my rules file?
No. File listings go stale immediately and waste context tokens. Dependency versions change with every update. Rules should contain stable conventions — how to code, not what code exists. Let the AI read your actual file system for structure, and use a context engine for dependency awareness. Only mention dependencies when the instruction is version-dependent, such as "Use Next.js App Router, not Pages Router."
Do Cursor rules replace the need for a context engine?
No — they solve different problems. Rules tell the AI HOW to write code (conventions, patterns, style). Context tells it WHAT code exists (function signatures, types, dependency relationships). Without context, the AI follows your rules but may duplicate existing code or miss existing helper functions. Without rules, it integrates with existing code but may use wrong conventions. Both together produce AI-generated code that is stylistically correct and functionally accurate.

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