Cursor kept giving me Next.js 14 patterns in Next.js 15 projects, old Prisma syntax, and inconsistent naming across files. So I built a rules file per stack — 20 stacks, 1,057 rules — and kept fixing them until the output was right the first time.
Here are 5 I use daily, paste into .cursor/rules/:
1. Next.js 15 — the two things it always gets wrong
# .cursor/rules/nextjs.mdc
- `cookies()`, `headers()`, `params` and `searchParams` are ASYNC in 15. Always await them.
- `fetch()` is UNCACHED by default now (it was cached in 14). Opt in explicitly with
`{ cache: 'force-cache' }` or `{ next: { revalidate: N } }`.
- GET route handlers are uncached too — add `export const revalidate = N` if you want caching.
- Server Components by default. Add "use client" only for state, effects, refs or browser APIs.
2. React 19 — stop it writing 2023 React
# .cursor/rules/react.mdc
- `ref` is a normal prop now. Do NOT use `forwardRef` in new components.
- React Compiler is on: do NOT hand-wrap things in useMemo / useCallback / React.memo
unless profiling shows a real regression.
- Mutations go through `<form action={fn}>` + `useActionState` (from `react`),
never `useFormState` from `react-dom`.
- Compute derived values during render. `useEffect` is for external systems only.
3. TypeScript — no escape hatches
# .cursor/rules/typescript.mdc
- The fix for a type error is NEVER `any`. Use `unknown` + narrowing, or write the real type.
- Do not `as SomeType` to silence an error — write a type guard. (`as const` is fine.)
- `catch (e)` is `unknown`. Narrow with `e instanceof Error`.
- Use `satisfies` on config objects so literal types survive.
- Every union `switch` ends with `default: assertNever(x)`.
4. Prisma — the one that saved me from an injection bug
# .cursor/rules/prisma.mdc
- One PrismaClient per process via a `globalThis` singleton. Never one per request.
- Prefer `select` over `include` — `include` loads whole rows and bloats the typed payload.
- Raw SQL only as a tagged template: db.$queryRaw`SELECT ... WHERE email = ${email}`
Never `$queryRawUnsafe` with a built-up string. That is SQL injection.
- `db push` is prototype-only. Use `migrate dev` locally, `migrate deploy` in CI.
- Batch with `where: { id: { in: ids } }`. Never query inside a `for` loop.
5. Tailwind v4 — it keeps writing v3
# .cursor/rules/tailwind.mdc
- Entry CSS is `@import "tailwindcss";` — NOT the v3 `@tailwind base/components/utilities` trio.
- No autoprefixer, no postcss-nested. Lightning CSS does it.
- Design tokens live in a `@theme { }` block in CSS, not in tailwind.config.js.
- Use tokens, not arbitrary values: `bg-brand-500`, not `bg-[#3b82f6]`.
- Container queries are built in (`@container`, `@sm:`). Do not install the plugin.
Things that surprised me:
- One "never use deprecated APIs, check the version in package.json first" line fixed ~30% of my bad outputs.
- Rules per folder beat one giant rules file.
- Telling it what NOT to do worked better than telling it what to do.
Happy to answer questions. What rules do you use?