How I transformed my Cursor rules for better AI coding
If you're using Cursor (and if you're not, you should be), you've probably discovered the magic of .cursor/rules files. But here's the thing – most of us start with massive, verbose rules that look impressive but quickly become unmaintainable nightmares.
If you’re using Cursor (and if you’re not, you should be), you’ve probably discovered the magic of .cursor/rules files. But here’s the thing – most of us start with massive, verbose rules that look impressive but quickly become unmaintainable nightmares.
I recently went through a complete overhaul of my Cursor Rules setup for a Next.js project, and the results were so good I had to share. Let me walk you through the transformation and the lessons learned.
The Problem: When Rules Become Rulers
My original rules looked like this typical mess:
---description: "typescript-patterns"globs: *.ts,*.tsx---
# TypeScript Patterns for CVPHOTO
## Type Safety Requirements
### Strict ConfigurationThe project uses strict TypeScript configuration via tsconfig.json:- `strict: true` is enabled- Never use `any` type - use `unknown` instead- Always provide explicit types for function parameters...
[500 more lines of verbose examples and explanations]Sound familiar? These rules had several problems:
- Overwhelming verbosity – Nobody wants to read a novel every time they need a quick pattern
- Stale code examples – Hard-coded snippets that went out of sync with the actual codebase
- Poor targeting – Basic glob patterns that didn’t leverage Cursor’s smart application
- Maintenance nightmare – Every code change meant updating multiple rule files
The Solution: Reference-Driven, Context-Aware Rules
Here’s what I transformed that rule into:
---alwaysApply: falsedescription: TypeScript patterns and code style conventionsglobs: ["**/*.{ts,tsx}", "!node_modules/**", "!.next/**"]---
## TypeScript Patterns & Code Style
### Type safety- Enable and honor strict mode; do not use `any`. Prefer `unknown` with proper narrowing.- Prefer discriminated unions over enums.- Use `readonly` for immutable properties/arrays.- Add explicit types for function params/returns and exported objects.
### Control flow & syntax- Use early returns and handle edge cases first.- Use optional chaining (`?.`) and nullish coalescing (`??`) appropriately.- Avoid `await` in return statements.
### Imports- Order: built-in → external → internal → parent → sibling → index → type- Add blank lines between groups; sort members alphabetically.
### File organization- Keep files under ~300 lines when possible; split by responsibility.- Centralize shared types in `src/types` or local `types.ts` next to implementations.Much better, right? But the real magic happens when you combine this with…
The Game Changer: Smart File References
Instead of copying code into rules, I started using Cursor’s mdc: reference system:
### Data & actions- Server actions: [src/action/](mdc:src/action/) (auth, user updates, payments)- Supabase clients: [server](mdc:src/utils/supabase/server.ts) (SSR-safe), [client](mdc:src/utils/supabase/client.ts) (browser)- AI workflows: [tune training](mdc:src/app/api/llm/tune/createTune.ts), [prompt generation](mdc:src/app/api/llm/prompt/createPrompt.ts)This approach has huge benefits:
- Living documentation – Rules always point to current code
- Single source of truth – No more keeping examples in sync
- Context-rich references – AI understands what each file actually does
- Reduced maintenance – Code changes automatically keep rules current
The Architecture: A Layered Approach
I organized my rules into three tiers:
1. Core Layer (Always Applied)
# project-structure-and-style.mdcalwaysApply: trueEssential conventions that apply everywhere. Keep this minimal – one rule max.
2. Context Layer (Auto-Applied via Globs)
# agents.mdc - Templates for components/APIsglobs: ["src/components/**/*.{ts,tsx}", "src/app/api/**/route.ts"]
# ui-components.mdc - Radix/Tailwind patternsglobs: ["src/components/**/*.{ts,tsx}", "src/app/**/page.tsx"]
# supabase-patterns.mdc - Database operationsglobs: ["src/action/**/*.ts", "src/utils/supabase/**/*.ts"]These rules activate automatically based on what files you’re working in. Smart!
3. Reference Layer (Manual Fetch)
# development-workflow.mdcalwaysApply: false# No globs - manual reference onlyDocumentation-heavy rules you fetch when needed.
Pro Tips I Learned Along the Way
1. Use Array-Based Globs
# ✅ Goodglobs: ["src/components/**/*.{ts,tsx}", "!**/*.test.ts"]
# ❌ Avoidglobs: "*.ts,*.tsx"Array format gives you more control and better exclusion patterns.
2. Add Context to File References
# ✅ Better- [src/action/auth.ts](mdc:src/action/auth.ts) (login, logout, session management)
# ❌ Basic- [src/action/auth.ts](mdc:src/action/auth.ts)Help the AI understand what each file contains.
3. Create a Rule Selection Guide
I added a README.md to my rules directory that maps common workflows to relevant rules:
### When Working On...
**🎨 UI Components & Pages**- Primary: `agents` (templates & standards)- Secondary: `ui-components` (Radix/Tailwind patterns)- Always: `typescript-patterns` (code style)
**🔧 API Endpoints**- Primary: `agents` (API templates)- Secondary: `api-integrations` (if Stripe/Astria)- Always: `typescript-patterns` (code style)4. Template Everything
My agents.mdc rule includes actual templates:
// Component templateexport type ButtonLikeProps = Readonly<{ label: string; onClick?: (event: MouseEvent<HTMLButtonElement>) => void; disabled?: boolean; className?: string;}>;
export function ButtonLike({ label, onClick, disabled = false }: ButtonLikeProps) { return ( <button type="button" aria-disabled={disabled} onClick={onClick} className={`inline-flex items-center...`} > {label} </button> );}This gives the AI concrete patterns to follow for new components.
The Results: Night and Day Difference
After the transformation:
- Rules are actually readable – No more 500-line documentation files
- Always current – File references mean rules never go stale
- Context-aware – Right rules activate at the right time
- Maintainable – Changes to one rule file, not scattered examples
- Effective – AI suggestions improved dramatically
Your Turn: The Migration Strategy
If you want to try this approach:
- Start with one rule – Pick your most verbose rule file
- Extract the essence – What are the core principles?
- Replace examples with references – Use
mdc:links to actual code - Add smart globs – Target specific file patterns
- Test and iterate – See how the AI responds to your changes
The goal isn’t to document everything – it’s to give the AI just enough context to make smart decisions while keeping your rules maintainable.
The Bottom Line
Good Cursor Rules aren’t about being comprehensive – they’re about being useful. The best rule is one that gives the AI exactly what it needs, when it needs it, without overwhelming the context window.
Your rules should feel like a helpful colleague whispering the right advice at the right moment, not a manual that requires a PhD to understand.
What’s your Cursor Rules setup like? Have you found patterns that work particularly well? I’d love to hear about your experiences in the comments.