Tinker AI
Read reviews
intermediate 8 min read

Cursor Rules: making the AI follow your project's actual conventions

Published 2026-04-25 by Owner

Out of the box, Cursor writes code that looks like the median of its training corpus. For a TypeScript project, that means React components in a slightly different style than yours, error-handling patterns that don’t match your codebase, and import orders that fight your linter. None of this is wrong; it’s just not yours.

Cursor Rules is the feature that fixes this. A .cursor/rules/ directory in your repo holds rule files that Cursor reads on every prompt. Done well, this turns “I have to fix the import order on every Cursor output” into “Cursor produces code my linter accepts on the first try.”

Where rules live

Cursor 0.45+ uses a directory structure:

.cursor/
└── rules/
    ├── general.mdc
    ├── react-components.mdc
    ├── api-routes.mdc
    └── tests.mdc

Each .mdc file is markdown with frontmatter that controls when the rule applies. You can have one general rule that always loads, plus narrower rules that only activate for specific file patterns.

The old .cursorrules single-file format still works but is deprecated. If you have one, migrate it to .cursor/rules/general.mdc and split out file-specific rules from there.

A general.mdc that earns its keep

This is the global rule loaded for every prompt. Keep it short — the longer it is, the more likely Cursor ignores parts of it.

---
description: General coding conventions for this project
globs:
alwaysApply: true
---

# Code conventions

- TypeScript strict mode is on. Never use `any`. If you genuinely don't know the type, use `unknown` and narrow.
- Imports: external first, then `@/*` aliased imports, then relative. Blank line between groups.
- Function components with arrow syntax: `const Foo = () => { ... }`. Never `function Foo()`.
- Async/await over `.then()`. No bare promises.
- Single quotes for strings except in JSX where double quotes match the surrounding HTML idiom.

# Don't suggest

- Adding `try/catch` around code that doesn't have a clear error contract. Let it propagate.
- Dependency-array `useEffect` for derived state. Use `useMemo` or compute inline.
- New libraries without checking existing imports first.

That’s about 200 words and covers the conventions I most often have to fix in Cursor’s output. The “Don’t suggest” section is more important than people realize — telling the model what NOT to do is often more effective than describing what to do.

File-pattern-specific rules

For rules that only apply to certain files, use the globs field:

---
description: React component conventions
globs: src/components/**/*.tsx
alwaysApply: false
---

# Component patterns

- Default export at the bottom of the file: `export default Foo`.
- Props type defined as `type FooProps = { ... }` directly above the component.
- No `React.FC` annotation — implicit return type only.
- For optional props, prefer default values in destructuring over `?:` and runtime checks.
- Use `cn()` from `@/lib/utils` for conditional classNames, not string concatenation.

# Patterns we deprecated

- Class components — never write a new one. Convert any encountered to functional form.
- `useCallback` without measured render-cost reason. Default to plain function references.

When Cursor edits a file under src/components/**/*.tsx, both general.mdc and react-components.mdc apply. Outside that pattern, only general.mdc does.

Rules that consistently move the needle

After about three months of refining rules across two projects, the categories that matter most:

Naming conventions. “Use camelCase for variables, PascalCase for types, SCREAMING_CASE for environment variables.” Cursor without this rule will mix conventions on the same line.

File location. “Helpers go in src/lib/, not in utils/.” If your project has historical naming you regret, the rule keeps Cursor from making it worse.

Library choices. “Use zod for validation, not yup or joi. We have an existing schema setup.” Without this, Cursor will sometimes pull in the second-most-popular library it knows about.

Error handling style. “Throw typed errors, don’t return { ok, error } Result tuples.” Or vice versa. Either is fine; mixing them in one codebase is worse than picking either.

Test patterns. Where tests live, what they’re named, what assertions to use. This single rule saves more time than any other for me, because untested code that Cursor adds is the most common follow-up work.

Rules that don’t help

Three things I tried that turned out to be wasted effort:

Telling it your stack. “We use Next.js 14, React 18, Tailwind, Postgres.” Cursor reads your package.json. Repeating this in a rule adds noise without adding signal.

Style preferences with subjective rationales. “We prefer functional components because they’re cleaner.” The “because” makes the rule longer without making it more enforceable. Just say “functional components only” and move on.

Rules longer than 50 lines. Cursor’s context budget for rules is finite. A 200-line rule file gets summarized internally and the specific guidance gets lost. Multiple smaller rules with file-pattern targeting work better than one mega-rule.

How to know your rules are working

The before-and-after I look for:

Before: Cursor’s output requires me to manually fix import order, rename a function, change a const to let, and swap function for arrow syntax. Maybe 30 seconds per accepted suggestion.

After: Cursor’s output passes my linter on first commit. The 30 seconds disappears. On a day with 20 accepted suggestions, that’s 10 minutes back.

If after a week of having rules in place you’re still doing the same fixes by hand, the rule isn’t being followed — usually because it’s too long or too vague. Trim it and try again.

Sharing rules with the team

.cursor/rules/ should be checked into git. This way:

  • New team members get the rules without configuring anything
  • When you change a convention, you update the rule and everyone benefits next pull
  • The rules become living documentation of what your code style actually is

The rules also serve as orientation for human contributors who want to know how to write code that fits the project. I’ve started linking new hires to .cursor/rules/general.mdc as a sort of style guide TL;DR.

What this isn’t

Cursor Rules is not a substitute for code review, type checking, or linting. The rules nudge the model toward conventions; they don’t enforce them. A linter that errors on any is more reliable than a rule that says “no any.”

Use rules to reduce friction. Use linting and CI to enforce correctness. Both, not either.