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:

.cursor/rules/typescript-patterns.mdc
---
description: "typescript-patterns"
globs: *.ts,*.tsx
---
# TypeScript Patterns for CVPHOTO
## Type Safety Requirements
### Strict Configuration
The 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:

  1. Overwhelming verbosity – Nobody wants to read a novel every time they need a quick pattern
  2. Stale code examples – Hard-coded snippets that went out of sync with the actual codebase
  3. Poor targeting – Basic glob patterns that didn’t leverage Cursor’s smart application
  4. 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:

.cursor/rules/typescript-patterns.mdc
---
alwaysApply: false
description: TypeScript patterns and code style conventions
globs: ["**/*.{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:

.cursor/rules/file-references.mdc
### 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)

.cursor/rules/project-structure.mdc
# project-structure-and-style.mdc
alwaysApply: true

Essential conventions that apply everywhere. Keep this minimal – one rule max.

2. Context Layer (Auto-Applied via Globs)

.cursor/rules/context-layer.mdc
# agents.mdc - Templates for components/APIs
globs: ["src/components/**/*.{ts,tsx}", "src/app/api/**/route.ts"]
# ui-components.mdc - Radix/Tailwind patterns
globs: ["src/components/**/*.{ts,tsx}", "src/app/**/page.tsx"]
# supabase-patterns.mdc - Database operations
globs: ["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)

.cursor/rules/development-workflow.mdc
# development-workflow.mdc
alwaysApply: false
# No globs - manual reference only

Documentation-heavy rules you fetch when needed.

Pro Tips I Learned Along the Way

1. Use Array-Based Globs

.cursor/rules/glob-example.mdc
# ✅ Good
globs: ["src/components/**/*.{ts,tsx}", "!**/*.test.ts"]
# ❌ Avoid
globs: "*.ts,*.tsx"

Array format gives you more control and better exclusion patterns.

2. Add Context to File References

.cursor/rules/reference-example.mdc
# ✅ 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:

.cursor/rules/README.md
### 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:

ButtonLike.tsx
// Component template
export 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:

  1. Start with one rule – Pick your most verbose rule file
  2. Extract the essence – What are the core principles?
  3. Replace examples with references – Use mdc: links to actual code
  4. Add smart globs – Target specific file patterns
  5. 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.