You know the pattern.
You open a new Cursor chat. You type "fix this bug". Cursor gives you JavaScript with no types, useEffect where a Server Component would work, and imports from next/router instead of next/navigation.
So you write: "actually use TypeScript strict mode, we're on App Router, and use next/navigation not next/router".
It fixes it. Next session: same explanation.
There's a better way.
.cursor/rules/ — persistent context for every chat
Cursor reads every .md file in your .cursor/rules/ folder at the start of each chat. It's like a system prompt, but per-project, and you write it once.
mkdir -p .cursor/rules
Now create .cursor/rules/nextjs.md:
## Next.js 14 App Router — Rules
- Server Components by default. Only add "use client" for interactivity or hooks.
- Named exports everywhere except page.tsx and layout.tsx.
- Use next/navigation (not next/router): useRouter, usePathname, redirect(), notFound().
- TypeScript strict mode. No `any`. Use `unknown` and narrow.
- Fetch in Server Components when possible — no useEffect + fetch for data.
- Tailwind: mobile-first, use cn() for conditional classes.
Open a new chat. Ask Cursor to "add a server action to fetch the current user". It'll do it correctly — typed, using auth() server-side, no client component — without you explaining anything.
Add more rules per concern
Keep each file focused on one topic:
.cursor/rules/stripe.md
## Stripe — Rules
- Read raw body with req.text() before signature verification — never parse as JSON first.
- Verify: stripe.webhooks.constructEvent(body, signature, STRIPE_WEBHOOK_SECRET).
- Handle webhooks idempotently — they can fire multiple times.
- Attach userId in metadata on checkout session AND subscription_data.metadata.
- Never trust amount from client — verify via webhook.
.cursor/rules/validation.md
## Zod Validation — Rules
- Validate all external input at API boundaries.
- Use z.safeParse() when input may be invalid.
- Infer TypeScript types: type User = z.infer<typeof UserSchema>
- Validate env vars at startup — crash early if misconfigured.
- Never use z.any().
.cursor/rules/security.md
## Security — Rules
- Verify auth server-side on every protected route — never trust client state.
- Check ownership on resource operations: if (post.authorId !== userId) throw Forbidden.
- Use parameterized queries — never string concatenation.
- Rate limit: 10 req/min per IP on auth endpoints.
- Security headers: X-Content-Type-Options, X-Frame-Options, CSP.
Rules vs CLAUDE.md
If you use Claude Code alongside Cursor:
-
.cursor/rules/→ Cursor reads these -
CLAUDE.md→ Claude Code reads this
They serve similar purposes but different tools. Keep them in sync.
What to put in rules
Good rules are actionable and specific:
✅ "Use z.safeParse() when input may be invalid"
✅ "Server Components by default — only add 'use client' when needed"
✅ "Validate env vars at startup with Zod — crash early"
Bad rules are vague:
❌ "Write clean code"
❌ "Follow best practices"
❌ "Be careful with security"
The test: could Cursor misinterpret this? If yes, make it more specific.
My full set of rules
I use 25 rule files across my projects — one per topic:
- Next.js 14 App Router
- React + TanStack Query + Zustand
- Python FastAPI + Pydantic v2
- Go idioms
- Prisma + PostgreSQL (query patterns, indexing)
- Redis caching
- Stripe webhooks
- Auth.js v5
- Tailwind + shadcn/ui
- Anthropic Claude API + OpenAI streaming
- Docker multi-stage builds
- Vercel deployment
- OWASP web security
- Vitest + React Testing Library
- Conventional Commits + Git workflow
- Zod validation
- REST API design
I packaged them up: Cursor Rules & AI Coding Config Pack — $19 on Gumroad
Includes CLAUDE.md templates for Claude Code users too.
What rules do you use in your projects? Drop them in the comments.
Top comments (0)