DEV Community

ofir saban
ofir saban

Posted on

How to write Cursor rules that the agent actually follows (with .mdc examples)

If you use Cursor every day, you've typed this more than once:

"No, we use the App Router." "Don't use any." "Put the test next to the file." "Validate with zod."

The agent isn't bad. It just doesn't know your project. Project rules are how you tell it once instead of every prompt. Here's the structure I've settled on after a lot of trial and error.

How Cursor rules work (current format)

Rules live in .cursor/rules/ as .mdc files: Markdown with a small frontmatter block.

---
description: "Next.js App Router conventions"
globs: "app/**/*,**/*.tsx"
alwaysApply: false
---
# Next.js (App Router)
...
Enter fullscreen mode Exit fullscreen mode

Three fields decide when a rule is loaded:

Setup When it loads Use for
alwaysApply: true Every request Small project core, code style
globs set When matching files are in context Framework / language rules
description only When the agent decides it's relevant Niche topics (migrations, i18n)
none Only when you @-mention it Rare procedures

(The old single .cursorrules file still works but is legacy. Split it up.)

A 5-file structure that works

.cursor/
  rules/
    00-project-core.mdc   # always: what this is + definition of done
    01-workflow.mdc       # always: git, small diffs, summarize changes
    02-security.mdc       # globs: server code
    05-code-style.mdc     # always: typing, tests, tone
    10-nextjs.mdc         # globs: app/**, *.tsx
  skills/
    code-review/SKILL.md
    write-tests/SKILL.md
PROMPTS.md
Enter fullscreen mode Exit fullscreen mode

Rule of thumb: always-on rules stay short (under ~40 lines each). Everything framework-specific is glob-scoped so it doesn't eat context when you're editing a SQL migration.

00-project-core.mdc

---
description: "Project context and definition of done"
alwaysApply: true
---
# Project
Acme Invoices: B2B invoicing SaaS. Next.js 15 App Router, Postgres (Drizzle), Tailwind.
Team of 3. Optimise for readable code over clever code.

# Definition of done
- Types pass, lint passes, tests for new logic.
- Loading and error states for every new route.
- End with a 3-bullet summary: what changed, what to verify, any risk.
Enter fullscreen mode Exit fullscreen mode

10-nextjs.mdc

---
description: "Next.js App Router conventions"
globs: "app/**/*,**/*.tsx"
alwaysApply: false
---
# Next.js (App Router)
## Server vs client
- Components are Server Components by default. Add "use client" only for state, effects or events.
- Never import DB clients or secrets into client code.
## Data
- Fetch in Server Components, not in useEffect.
- Mutations → Server Actions, validated with zod, then revalidatePath().
- Be explicit about caching on every fetch.
## Env
- Only NEXT_PUBLIC_ vars reach the browser.
Enter fullscreen mode Exit fullscreen mode

02-security.mdc

---
description: "Security rules for server code"
globs: "app/**/actions.ts,app/api/**,lib/server/**"
alwaysApply: false
---
- Every mutation checks auth AND ownership of the resource.
- Validate all input at the boundary (zod). Never trust client IDs.
- No secrets in logs, errors or client bundles.
Enter fullscreen mode Exit fullscreen mode

Rules vs skills

Rules say how code should look. Skills are step-by-step procedures the agent follows when you ask for a task, like "review this diff" or "write tests for this module". Keeping procedures out of rules keeps your always-on context small.

# skills/write-tests/SKILL.md
1. Read the module and list its public behaviours.
2. Write one test per behaviour + edge cases (empty, invalid, unauthorized).
3. Colocate as *.test.ts, use Vitest, no snapshot tests for logic.
4. Run tests; fix failures in the code only if the test reveals a real bug.
Enter fullscreen mode Exit fullscreen mode

Add a prompt library

The same tasks come up every week. Save the prompts that work:

  • Debug: "Find the root cause (not just the symptom), explain it in 2–3 sentences, apply the fix and add a regression test."
  • Plan: "Before writing code, list the files you'll touch and why. Wait for my OK."
  • PR: "Write a PR description: why, what changed, how to test, risks."

Common mistakes

  1. One giant always-on file. It costs context and the agent skims it.
  2. Vague rules ("write clean code"). Be concrete: "no default exports", "tests next to the file".
  3. No globs. Your Python rules load while you edit CSS.
  4. Contradictions between rules. The agent picks one at random.
  5. Never updating them. When you correct the agent twice, add a rule.

The shortcut

Writing all of this for every project takes a while, so I built VibeRules. You describe your project (stack, team size, testing, styling), preview every file, and download a ready .cursor/ folder with 6–8 rules, 3 skills and a prompt library. It supports Next.js, React + Vite, Node, FastAPI and full-stack TypeScript, and runs entirely in your browser with no signup. It's free during early access.

Even if you write your own rules, I hope this structure saves you some trial and error.

What's the one rule you always add to a new project? Drop it in the comments. I'm collecting the best ones.

Top comments (0)