Skip to content

Web Development · Developer Tools

Cursor Rules for Teams: Codifying Your Standards for AI Coding

Cursor's rules system encodes your team's architecture, naming, and coding standards into the AI's context, so every engineer gets consistent suggestions.

Anurag Verma

Anurag Verma

8 min read

Cursor Rules for Teams: Codifying Your Standards for AI Coding

Sponsored

Share

AI coding assistants are only as good as the context they have. By default, Cursor knows the patterns in your open file and whatever it can infer from the rest of the codebase. It doesn’t know that your team uses Zod for validation instead of Yup, that service functions should never be called directly from route handlers, or that you have a wrapper around fetch that handles error logging.

Cursor rules fix that. They inject persistent context into every AI interaction in your project: how your specific codebase works, what patterns you follow, and what the AI should and shouldn’t suggest.

How Cursor Rules Work

Cursor moved from a single .cursorrules file to a .cursor/rules/ directory in late 2024. The directory format is more flexible: you can have multiple rule files, apply them conditionally based on file path, and toggle some off when not needed.

Rule files use the .mdc extension (Markdown with Cursor metadata). The frontmatter controls when and how the rule applies:

---
description: TypeScript and React conventions
globs: ["**/*.ts", "**/*.tsx"]
alwaysApply: false
---

## Code Style

Use named exports, not default exports.
Prefer `interface` over `type` for object shapes.
Use `const` everywhere; never `let` for primitives.

## Component Structure

React components follow this structure:
1. Type definitions
2. Component function
3. Local hooks/helpers
4. Export

Components must not import from other feature directories.
Cross-feature dependencies go through the shared/ layer.

The globs field limits when Cursor loads this rule file. A rule file with globs: ["**/*.ts", "**/*.tsx"] only applies when you’re working in TypeScript/TSX files. A rule file with alwaysApply: true loads for every file in the project.

In a monorepo, Cursor also supports nested .cursor/rules/ directories: a rule file placed inside packages/web/.cursor/rules/ only applies to files under that package, while a .cursor/rules/ directory at the repo root still applies everywhere. This matters once a monorepo has packages with genuinely different conventions, for example a legacy package still on class components next to a newer package built entirely on React Server Components — a single global rule file would give the AI contradictory guidance for both.

Directory Layout

A well-organized .cursor/rules/ directory for a Next.js project:

.cursor/
  rules/
    architecture.mdc       # alwaysApply: true (project structure, invariants)
    typescript.mdc         # globs: **/*.ts, **/*.tsx
    api-routes.mdc         # globs: app/api/**/*.ts
    database.mdc           # globs: lib/db/**/*.ts, **/*.sql
    testing.mdc            # globs: **/*.test.ts, **/*.spec.ts
    react-components.mdc   # globs: **/*.tsx

The architecture.mdc file sets the foundation that every other rule builds on:

---
description: Project architecture overview
alwaysApply: true
---

## Project Overview

E-commerce platform. Next.js 15 App Router. PostgreSQL via Drizzle ORM. 
Stripe for payments. Resend for transactional email.

## Layer Rules

- UI: app/ directory, .tsx files only, React Server Components by default
- API: app/api/ for REST endpoints, server actions for form submissions
- Business logic: lib/ directory, pure TypeScript functions
- Database: lib/db/, Drizzle schema and queries only, never raw SQL in routes
- External services: lib/integrations/, each service gets its own file

Business logic must not import from app/ (no circular dependencies).
Database queries must not be called from UI components; always go through lib/.

## Error Handling

All async functions return { data, error } objects. Never throw in business logic.
Server actions use next/navigation redirect() for success paths.
API routes return NextResponse.json() with consistent { success, data, error } shape.

## No-Go Zones

- Never use `any` type (use `unknown` and narrow)
- Never use `console.log` in production paths (use the logger at lib/logger.ts)
- Never call Stripe directly from components (always via lib/integrations/stripe.ts)

TypeScript-Specific Rules

A TypeScript rule file typically documents the same conventions you’d otherwise repeat in every code review: which validation library to use (Zod is common; our guide to Zod schema validation in production covers the patterns worth codifying), how errors propagate, and when to write explicit return types.

---
description: TypeScript patterns and conventions
globs: ["**/*.ts", "**/*.tsx"]
alwaysApply: false
---

## Types and Interfaces

Use `interface` for object types that describe shapes.
Use `type` for unions, intersections, and utility types.

Prefer explicit return types on exported functions:
```typescript
// Good
export function getUser(id: string): Promise<User | null> { ... }

// Avoid
export function getUser(id: string) { ... }

Zod is used for runtime validation. Define schemas in the same file as the business logic that uses them. Export inferred types:

const CreateOrderSchema = z.object({
  items: z.array(z.object({ productId: z.string(), quantity: z.number().int().positive() })),
  shippingAddressId: z.string(),
});
type CreateOrderInput = z.infer<typeof CreateOrderSchema>;

Async/Error Patterns

This project uses the Result pattern for error handling:

type Result<T, E = Error> = { ok: true; data: T } | { ok: false; error: E };

The helper is at lib/result.ts. Always use it for functions that can fail. Never use try/catch in service functions; let errors propagate to the boundary.


## Component Rules for React

React rules tend to focus on the server/client boundary, since that's the decision the AI gets wrong most often without guidance (see [our deep dive on why server components are everywhere now](/blog/server-components-are-everywhere-now/) for the underlying reasoning).

```markdown
---
description: React component conventions
globs: ["**/*.tsx"]
alwaysApply: false
---

## Server vs Client Components

Default to Server Components. Add `"use client"` only when the component:
- Uses hooks (useState, useEffect, useRef, etc.)
- Attaches event listeners
- Uses browser-only APIs

Mark the boundary as low in the tree as possible. Don't make a parent 
component a Client Component just because one child needs it.

## Props and Types

All component props use TypeScript interface:
```typescript
interface ButtonProps {
  variant: "primary" | "secondary" | "ghost";
  size?: "sm" | "md" | "lg";
  disabled?: boolean;
  children: React.ReactNode;
  onClick?: () => void;
}

Don’t use React.FC or React.FunctionComponent; just define the function directly.

Data Fetching

Components fetch their own data in Server Components:

export default async function OrderList({ userId }: { userId: string }) {
  const orders = await getOrdersByUser(userId);
  return <ul>{orders.map(order => <OrderItem key={order.id} order={order} />)}</ul>;
}

No prop-drilling of data from page to child components when the child can fetch its own data server-side.


## Committing Rules to Version Control

Cursor rules go in the repository. Everyone on the team gets the same rules when they clone the project.

```bash
git add .cursor/
git commit -m "chore: add cursor rules for project conventions"

Add a note in your README or onboarding docs explaining what the rules cover. New engineers should know these files exist and read them before diving into code.

What Makes a Good Rule

Rules that help:

  • Explain patterns that aren’t obvious from reading the code (“we use X instead of Y because Z”)
  • Describe structural invariants (“database queries only in lib/db/, never in routes”)
  • Define naming conventions (“service functions use verb-noun: getUser, createOrder, deleteSession”)
  • Specify which libraries handle which concerns (“use Resend for email, not Nodemailer”)
  • Describe error handling patterns that the codebase uses throughout

Rules that don’t help:

  • Generic advice the AI already knows (“write clean code”, “use meaningful variable names”)
  • Documentation about how a library works (the AI already knows that)
  • Rules so long that the relevant part gets diluted by noise
  • Contradictory rules in different files

Keep rule files short. A rule file that’s 50 lines with specific, project-specific guidance is more useful than a 300-line file with a mix of project-specific and generic advice.

Comparing with GitHub Copilot Instructions

GitHub Copilot has a similar feature: .github/copilot-instructions.md. It’s a single file that applies globally to all Copilot interactions in the repository.

Cursor’s rules system is more granular (different rules for different file types) and the MDC frontmatter gives you more control over when rules load. Copilot’s approach is simpler to set up but less precise. If your team uses Copilot, the .github/copilot-instructions.md file is worth maintaining with your project’s key conventions. The principle is the same — we compare the two tools more broadly, alongside Claude Code, in our look at the agentic IDE wars and our head-to-head of Cursor, Copilot, and Claude Code.

The Compounding Return

The value of Cursor rules compounds as the project grows. Early on, the AI can infer a lot from a small, consistent codebase. Later, with hundreds of files, patterns accumulate and drift. New features might introduce a new pattern for error handling. A different engineer might reach for a different library. Without rules, the AI starts suggesting based on whatever it sees in the local context, which may be inconsistent.

Rules make the AI’s suggestions match the codebase’s actual conventions rather than the AI’s training defaults. For teams where multiple engineers are using AI assistance simultaneously, that consistency is worth the hour it takes to write the initial rule files.

The other return: rules are good documentation. A new engineer reading .cursor/rules/architecture.mdc gets a faster mental model of the project than reading the codebase cold. The rules describe the “why” behind patterns in a way that code doesn’t.

Frequently asked questions

What replaced Cursor's old .cursorrules file?
In late 2024, Cursor moved from a single `.cursorrules` file to a `.cursor/rules/` directory containing multiple `.mdc` (Markdown with Cursor metadata) files. This lets teams apply different rules conditionally by file path instead of loading one flat file for every interaction.
How do globs and alwaysApply control when a Cursor rule loads?
The `globs` field in a rule file's frontmatter restricts it to matching file paths, such as `**/*.ts` for TypeScript-only rules. Setting `alwaysApply: true` instead loads that rule for every file in the project, which is typically reserved for foundational rules like project architecture.
What makes a Cursor rule actually useful versus noise?
Useful rules explain non-obvious, project-specific patterns: why a library was chosen over an alternative, which layer owns a responsibility, or a naming convention the team follows. Generic advice the AI already knows, library documentation, and overly long files that dilute the specific guidance all make rules less effective.
How does Cursor's rules system compare to GitHub Copilot instructions?
GitHub Copilot uses a single global file, `.github/copilot-instructions.md`, that applies to every Copilot interaction in the repository. Cursor's rules system is more granular, letting different rule files apply to different file types via globs, which gives more precise control at the cost of slightly more setup.
Should Cursor rules be committed to version control?
Yes. Committing `.cursor/rules/` to the repository ensures every engineer gets the same AI context the moment they clone the project, rather than each person relying on ad hoc local configuration. It also functions as documentation, giving new engineers a faster mental model of the codebase's conventions.

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored