Custom Rules & Project Configuration
Learning Outcomes
- Understand what .cursor/rules does and where it lives in your project
- Write effective rules that shape AI-generated code
- Create project-specific instructions for framework conventions and naming patterns
- Share rules across team members via version control
- Evolve rules over time as your project grows and patterns change
Lesson Plan
| Segment | Duration | Topic |
|---|---|---|
| Intro | 4 min | Why default AI output needs project-specific guidance |
| Demo 1 | 8 min | Creating your first rule files |
| Demo 2 | 8 min | Writing rules for code style and framework conventions |
| Explain | 5 min | How rules interact with context and prompts |
| Demo 3 | 8 min | Team sharing and version control strategies |
| Demo 4 | 7 min | Evolving rules — adding, refining, and removing over time |
| Wrap-up | 5 min | Best practices and key takeaways |
Before You Begin
Pre-work:
- Complete Lesson 5 — Debugging with Cursor
- Have a project with established coding conventions (naming, file structure, patterns)
- Identify 3-5 conventions your team follows that AI might not know about
Shopping List:
- A project open in Cursor (preferably one with a style guide or established patterns)
- Access to create files in the project root directory
- Examples of "good" code in your project that follows your conventions
Cursor rules are .mdc files in the .cursor/rules/ directory of your project. When Cursor's AI generates code, it reads these rules and follows the instructions inside them — treating them as persistent context for every interaction.
Where they live:
your-project/
├── .cursor/
│ └── rules/
│ ├── general.mdc ← project-wide conventions
│ ├── typescript.mdc ← TypeScript-specific rules
│ └── testing.mdc ← testing conventions
├── .gitignore
├── package.json
├── src/
│ └── ...
What they do:
- Provide persistent instructions to the AI model across all interactions (Ask, Cmd+K, Agent)
- Act like a system prompt that's always active when you're working in this project
- Override default AI behaviour with your specific preferences
- Apply to everyone who opens the project in Cursor (if committed to version control)
How they differ from one-off prompts:
| Approach | Persistence | Scope | Use Case |
|---|---|---|---|
| Ask/Agent message | One conversation | Current chat | Ad-hoc questions |
| Cmd+K prompt | One generation | Current edit | Specific code generation |
.cursor/rules/*.mdc |
Always active | Entire project | Team conventions |
AGENTS.md |
Always active | Entire project | Alternative (plain markdown) |
Creating your first rule file:
- Open the terminal in Cursor with Ctrl+`
- Run:
mkdir -p .cursor/rules && touch .cursor/rules/general.mdc - Open the file and add your rules
- Open the terminal in Cursor with Ctrl+`
- Run:
mkdir -p .cursor/rules && echo. > .cursor/rules/general.mdc - Open the file and add your rules
Rule file format (.mdc):
---
description: General project conventions
globs: "**/*"
alwaysApply: true
---
# Project Conventions
- Use TypeScript strict mode
- Follow camelCase for variables
- All functions must have JSDoc comments
The frontmatter controls when the rule applies:
description— what this rule is for (shown in settings UI)globs— which files this rule applies to (e.g.,"src/**/*.ts")alwaysApply: true— include in every interaction regardless of file context
Good rules are specific, actionable, and concise. The AI model responds best to clear instructions that describe what you want — not vague aspirations.
Rule writing principles:
- Be specific — "Use camelCase for variables" beats "Use good naming"
- Be positive — "Always use functional components" beats "Don't use class components"
- Give examples — Show the pattern you want, not just describe it
- Stay concise — The model has limited context; don't waste it on filler
- Prioritise — Put the most important rules first
Example: A basic rule files
# Project Conventions
## Language & Framework
- This is a Next.js 14 project using the App Router
- Use TypeScript for all new files
- Use Server Components by default; add "use client" only when needed
## Code Style
- Use named exports, not default exports
- Use arrow functions for components: export const MyComponent = () => {}
- Prefer const over let; never use var
- Use early returns to reduce nesting
## Naming
- Components: PascalCase (e.g., UserProfile)
- Files: kebab-case (e.g., user-profile.tsx)
- Hooks: camelCase with "use" prefix (e.g., useUserData)
- Types/Interfaces: PascalCase with no prefix (e.g., User, not IUser)
## Structure
- Place components in src/components/
- Place utilities in src/lib/
- Place API routes in src/app/api/
- Co-locate tests with source files (Component.test.tsx next to Component.tsx)
What makes rules effective — before and after:
| Weak Rule | Strong Rule |
|---|---|
| "Write good code" | "All functions must have TypeScript return types" |
| "Handle errors properly" | "Wrap async operations in try/catch and log errors with console.error" |
| "Use modern JavaScript" | "Use ES2022+ features: optional chaining, nullish coalescing, array.at()" |
| "Follow best practices" | "Extract reusable logic into custom hooks under src/hooks/" |
Testing your rules:
After writing rules, test them:
- Open Chat and ask: "Create a new React component for a user profile card"
- Check if the output matches your rules (naming, exports, structure)
- If not, refine the rules and try again
Different projects have different patterns. Your rules should reflect the specific decisions your team has made — not generic best practices.
Framework-specific rules:
For a React + Zustand project:
## State Management
- Use Zustand for global state (not Redux, not Context for shared state)
- Create stores in src/stores/ with the pattern: useXxxStore.ts
- Use selectors to avoid unnecessary re-renders:
const count = useCountStore((state) => state.count)
- Never access the store outside of React components — use store.getState() only in tests
For a Python FastAPI project:
## API Design
- Use Pydantic v2 models for all request/response bodies
- Place route handlers in src/routes/ grouped by resource
- Use dependency injection for database sessions
- Return proper HTTP status codes: 201 for creation, 204 for deletion
- All endpoints must have docstrings (shown in OpenAPI docs)
## Error Handling
- Raise HTTPException with detail messages, never return raw errors
- Use custom exception handlers in src/exceptions.py
- Log errors with structlog, not print() or logging module directly
For a Vue 3 + Composition API project:
## Vue Conventions
- Use <script setup lang="ts"> for all SFCs
- Use composables (useXxx) for reusable logic, placed in src/composables/
- Use defineProps with TypeScript types, not runtime validation
- Prefer computed() over watch() where possible
- Use Pinia stores in src/stores/, one store per domain concept
Architecture rules:
Beyond code style, encode your architecture decisions:
## Architecture Rules
- Components should not make API calls directly
- All data fetching goes through React Query hooks in src/hooks/queries/
- Domain logic lives in src/services/ — never in components
- Components are pure view layer: receive data via props, emit events up
- Maximum component file length: 150 lines. If longer, decompose.
Dependency rules:
## Dependencies
- Do not add new npm packages without team discussion
- Prefer native browser APIs over library solutions (e.g., fetch over axios)
- Use date-fns for date manipulation (not moment, not dayjs)
- For icons, use Lucide React — do not import from other icon libraries
One of the most powerful aspects of .cursor/rules is team alignment. When everyone uses the same rules, AI-generated code is consistent regardless of who generates it.
Version control strategy:
The rule files should be committed to your repository:
git add .cursor/rules/
git commit -m "Add AI coding rules for team consistency"
What to include in .gitignore (personal preferences):
Some teams create a personal rules file that's git-ignored:
# .gitignore
.cursor/rules.local # Personal preferences not shared with team
Then reference both: the shared .cursor/rules applies to everyone, while .cursor/rules.local (if Cursor supports it) or Cursor Settings > Rules for AI handles personal preferences.
Team adoption workflow:
- Start small — Begin with 5-10 rules that the team already agrees on
- Review in PR — Treat rule file changes like code changes; discuss in PRs
- Document reasoning — Add comments explaining why a rule exists:
# We use named exports because they provide better IDE refactoring
# support and prevent ambiguous default import names.
- Use named exports, not default exports
- Iterate together — Schedule a monthly review of the rules file
Onboarding benefit:
When a new developer joins the team:
- They clone the repo
- The rule files is already there
- Their AI immediately generates code that follows team conventions
- Fewer PR review comments about style and patterns
Rules for different environments:
If your monorepo has multiple apps with different conventions:
your-monorepo/
├── .cursor/rules/ ← shared rules for entire repo
│ └── general.mdc
├── apps/
│ ├── web/
│ │ └── .cursor/rules/ ← web-specific rules
│ │ └── web.mdc
│ └── mobile/
│ └── .cursor/rules/ ← mobile-specific rules
│ └── mobile.mdc
└── packages/
└── shared/
└── .cursor/rules/ ← shared library rules
└── library.mdc
Cursor uses rules from the .cursor/rules/ directory closest to the file you're editing. If you're working in apps/web/src/, it reads rules from apps/web/.cursor/rules/. Nested directories are supported.
Your rule files is a living document. As your project grows, your rules should grow with it — but also be pruned when they're no longer relevant.
When to add new rules:
- You notice the AI repeatedly generating code in a pattern you don't want
- The team adopts a new library or convention
- A bug was caused by a pattern you want to prevent
- New team members keep getting the same PR feedback
When to remove rules:
- A rule references a library you've migrated away from
- A rule is so obvious the AI always follows it without being told
- A rule conflicts with a newer decision
- The file is getting too long (aim for under 100 lines)
Evolving a rules file — real example:
Month 1 (project start):
- Use TypeScript
- Use React functional components
- Use CSS modules for styling
Month 3 (patterns established):
- Use TypeScript with strict mode
- Use React functional components with arrow function syntax
- Use CSS modules with camelCase class names
- Place shared components in src/components/common/
- Use React Query for all server state
- Use Zustand for client state
Month 6 (team growing, conventions mature):
## TypeScript
- Strict mode enabled; never use 'any' — use 'unknown' and narrow
- Define API response types in src/types/api/
- Use discriminated unions for state machines
## Components
- Arrow function components with named exports
- Props interface defined above component: interface XxxProps {}
- Maximum 150 lines per component file
- Decompose into sub-components in a folder: Component/index.tsx, Component/Header.tsx
## Data Fetching
- React Query for all server state; queries in src/hooks/queries/
- Mutations in src/hooks/mutations/
- Optimistic updates for user-facing actions
- Error boundaries at route level
## Testing
- Co-locate test files: Component.test.tsx
- Use React Testing Library; test behaviour, not implementation
- Mock API calls with MSW, not jest.mock
Measuring effectiveness:
Track these signals to know if your rules are working:
- Fewer style-related PR comments over time
- New team members ramp up faster
- AI output requires less manual correction
- Generated code passes linting on the first try
Maintaining rule quality:
# GOOD: Specific, testable, actionable
- Error messages must be user-friendly strings, not raw Error objects
- All API endpoints must validate input with zod schemas
- Database queries must use parameterized values, never string interpolation
# BAD: Vague, subjective, unverifiable
- Write clean code
- Follow best practices
- Keep things simple
Once you're comfortable with basic rules, these advanced patterns help you get even more precise output from the AI.
Pattern 1: Show, don't just tell
Include a short code example in your rules:
## Component Pattern
Every component should follow this structure:
interface ComponentProps {
// props here
}
export const Component = ({ prop1, prop2 }: ComponentProps) => {
// hooks first
// derived state
// handlers
// early returns for loading/error
// main return
}
Pattern 2: Conditional rules
## Conditional Conventions
- For files in src/pages/: Use default exports (Next.js requirement)
- For files everywhere else: Use named exports
- For test files: Use describe/it blocks, not test()
- For utility functions: Always include JSDoc with @param and @returns
Pattern 3: Anti-patterns (what NOT to generate)
## Anti-Patterns — Do Not Generate
- No useEffect for data fetching (use React Query instead)
- No prop drilling more than 2 levels (use composition or context)
- No inline styles (use CSS modules or Tailwind classes)
- No console.log in committed code (use our logger utility)
- No relative imports going up more than 2 levels (use path aliases @/)
Pattern 4: Technology constraints
## Technology Decisions (Do Not Override)
- Database: PostgreSQL via Prisma ORM (not raw SQL, not Knex, not TypeORM)
- Auth: NextAuth.js with JWT strategy (not custom auth, not session-based)
- Deployment: Vercel (do not suggest Docker or AWS-specific solutions)
- Node version: 20 LTS (do not use features unavailable in Node 20)
Pattern 5: Documentation rules
## Documentation
- Public functions must have JSDoc comments
- Complex algorithms must have a comment explaining the approach
- TODO comments must include a ticket number: // TODO(PROJ-123): description
- Do not generate comments that merely restate the code
Combining .cursor/rules with @-mentions:
Your rules set the baseline; @-mentions add context for specific requests:
.cursor/rulessays: "Use React Query for server state"- Your prompt says: "Create a hook to fetch user data @src/types/user.ts @src/lib/api.ts"
- The AI follows the rules AND uses the referenced files for types and API patterns
Questions & Answers
Key Takeaways
- .cursor/rules lives in project root and provides persistent AI instructions for every interaction
- Be specific and actionable — "Use named exports" is better than "Follow best practices"
- Include examples — showing the pattern you want is more effective than describing it
- Commit to version control — team alignment is one of the biggest benefits
- Evolve over time — add rules when you see repeated issues; remove rules that are no longer relevant
- Keep it concise — aim for under 100 lines; prioritise the most important rules first
Next Steps: In Lesson 7 — Agent Mode, you'll learn how to use Cursor's autonomous Agent mode to complete complex multi-step tasks with minimal intervention.