Cursor rules that actually work
Cursor reads two kinds of rule files: the legacy .cursorrules at the project root, and the newer per-feature .cursor/rules/*.mdc with front-matter. This guide explains both, what to put in them, and the common mistakes that cause Cursor to ignore your rules entirely.
The two formats
| Format | Path | Scope |
|---|---|---|
| .cursorrules | Project root | Whole repo, always-on. Older, still supported. |
| .cursor/rules/*.mdc | .cursor/rules/ folder | Per-feature, scoped by glob via front-matter. Newer, recommended. |
| User-level .cursor/rules/*.mdc | ~/.cursor/rules/ | Applies across all your repos. |
The .mdc format
---
description: Rules for the React frontend.
globs:
- "src/**/*.tsx"
- "src/**/*.ts"
alwaysApply: false
---
# React conventions
- Functional components only, hooks only.
- Tailwind for styling, no CSS modules.
- TanStack Query for server state, Zustand for client state.
- No useEffect for data fetching — use Query.
Three front-matter fields matter:
description— Cursor uses this to summarize the rule in chat.globs— file patterns this rule applies to. Cursor only loads rules whose globs match the files currently open or referenced.alwaysApply— whentrue, the rule loads regardless of which files are open. Use sparingly.
The common mistakes
- Writing 2000 words of prose. Cursor doesn't read essays — it reads rules. Use short, declarative bullets. "X, not Y" beats "we generally prefer X because…".
- No globs. Without globs, your rule never auto-loads. Set globs or use
alwaysApply: true. - Conflicting rules. Two .mdc files saying "use Zustand" and "use Jotai" → Cursor picks one and you blame the tool. Resolve conflicts at the project level.
- Generic "be a good engineer" rules. They don't change behavior. Be specific: "no useEffect for data fetching", not "write clean code".
- Forgetting to commit. Rules in
.cursor/rules/belong in git. Teammates need them too.
A working .cursorrules template
# Project rules
## Stack
- Frontend: React 19 + Vite 8 + TypeScript 5 + Tailwind 4 + shadcn/ui
- Backend: Fastify + Prisma + Postgres
- Tests: Vitest (unit), Playwright (e2e)
- Package manager: pnpm
## Workflow
- Plan before code. Write PLAN.md when scope > one file.
- Failing test first. No green, no commit.
- One feature per commit. Conventional commits.
- 3 retries then ask. Don't grind forever.
- Quality gate: lint + types + tests + build all green = "done".
## Style
- Functional components only. Hooks only.
- Tailwind utility classes. No CSS modules, no styled-components.
- Server state with TanStack Query. Client state with Zustand.
- No useEffect for data fetching.
- No emojis in code or output.
- Latest stable library versions.
## Banned
- Defensive over-engineering. No try/except wrappers without a reason.
- Workarounds. Find root cause, then fix.
- Mega-PRs. One feature per commit.
- Secrets in code. Use .env / .env.example.
Keep it in sync with your other agents
If you use Cursor and Claude Code and Copilot, you don't want three drift-prone copies. Generate them all from one template instead:
myVibe ships a per-project initializer that creates .cursorrules, AGENTS.md, CLAUDE.md, .windsurfrules, and .github/copilot-instructions.md from one template, so every agent reads the same rules.
pwsh -File ~/.agents/skills/myvibe/myvibe-init.ps1 # Windows
bash ~/.agents/skills/myvibe/myvibe-init.sh # macOS / Linux
One-line install
# Windows
iwr https://raw.githubusercontent.com/Mohamed201389/myVibe/main/bootstrap.ps1 | iex
# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/Mohamed201389/myVibe/main/bootstrap.sh | bash