# .cursorrules and .cursor/rules: examples that hold up > How Cursor reads its rules files, a template for a Next.js project, what to generate versus write by hand, and how to keep the rules from going stale. Source: https://autoplans.dev/guides/cursorrules ## .cursorrules vs .cursor/rules: what Cursor reads today Cursor reads standing project instructions from four places: the `.cursor/rules` directory, a root `AGENTS.md`, a root `CLAUDE.md`, and the legacy `.cursorrules` file. This guide is mostly about the first and the last, because that is the migration most repositories still have in front of them, and the two are not interchangeable. The `.cursorrules` file is the original format: one plain-text file in the project root, no frontmatter, no scoping. Everything in it applies to every request, whether the agent is editing a React component or a shell script. Cursor's help documentation calls the file legacy and says it will be deprecated, so the practical answer to how to write .cursorrules today is that you migrate it, in the four steps that page gives: create a new rule from the command palette, copy the `.cursorrules` content into it, set the rule type to Always Apply — which matches the old behaviour — and delete the flat file ([cursor.com/help/customization/rules](https://cursor.com/help/customization/rules)). The `.cursor/rules` directory holds `.mdc` files, version-controlled with the code and organised in subfolders if you want them. Each is Markdown with a YAML frontmatter block, and the frontmatter is the point: a rule attaches to every session, to files matching a glob, to requests the agent judges relevant, or only when you name it in chat. When a rule applies, its contents are included at the start of the model context. | | .cursorrules | .cursor/rules | |---|---|---| | Where | One file in the project root | `.cursor/rules/*.mdc`, subfolders allowed | | Scoping | None; the whole file applies to everything | Per file: always, by glob, by description, or manual | | Format | Plain text | Markdown with `description`, `globs`, `alwaysApply` frontmatter | | In Cursor's current docs | Documented as legacy, with migration steps | The documented project-rule format | Two other layers sit around project rules. User Rules are global to your Cursor environment, under Customize → Rules; Team Rules are managed from the dashboard on the Team and Enterprise plans. The documented order is Team Rules, then Project Rules, then User Rules — all applicable rules are merged, and earlier sources win where guidance conflicts ([cursor.com/docs/rules](https://cursor.com/docs/rules)). Cursor also reads a plain `AGENTS.md` in the project root and in subdirectories, a nested file combining with its parents and the more specific instruction taking precedence; if your instructions need no scoping, that may be enough. It reads a root `CLAUDE.md` the same way, and the help page adds that `CLAUDE.md` applies to every conversation whatever the `alwaysApply` frontmatter says ([cursor.com/help/customization/rules](https://cursor.com/help/customization/rules)). See [AGENTS.md](/guides/agents-md) for what belongs in either. ## Rule anatomy: the three frontmatter fields Every `.mdc` file starts with a frontmatter block. Three fields decide how the rule is attached. ```markdown --- globs: src/app/api/**/*.ts alwaysApply: false --- Body of the rule, in Markdown. ``` The documented combinations: | Frontmatter | Rule type | When it is attached | |---|---|---| | `alwaysApply: true` | Always Apply | Every session | | `alwaysApply: false` with `globs` | Apply to Specific Files | When a file matching the pattern is in context | | `alwaysApply: false` with `description` | Apply Intelligently | When the agent reads the description and decides it is relevant | | `alwaysApply: false`, neither `globs` nor `description` | Apply Manually | Only when you `@`-mention the rule in chat | The rows are exclusive. Cursor's reference gives `description` for Apply Intelligently and `globs` for Apply to Specific Files, and its auto-attached example carries `globs` and `alwaysApply` and no description; a rule that sets both fields is not one of the documented combinations, so set one or the other ([cursor.com/docs/rules](https://cursor.com/docs/rules)). Globs use the usual syntax: `**/*.ts` for every TypeScript file, `src/**` for everything under `src/`. Inside the body, `@filename.ts` includes a file in the rule's context — how you point at a canonical example instead of pasting it. The documented guidance on size: keep a rule under 500 lines, split large ones into composable rules, and cover patterns you use often rather than edge cases. ## A complete Cursor project rules example for Next.js and TypeScript This is a working layout for a Next.js App Router project. One always-on rule carries the facts every request needs; the rest attach by glob or description, so the agent sees them only when they matter. ```text .cursor/rules/ project.mdc # alwaysApply: stack, commands, layout app-router.mdc # globs: src/app/**/*.tsx api-routes.mdc # globs: src/app/api/**/route.ts testing.mdc # description only; the agent decides ``` `project.mdc` is the only one the agent always reads, so it stays short: ```markdown --- alwaysApply: true --- Acme dashboard: Next.js App Router, TypeScript strict, Prisma on PostgreSQL, Vitest. Package manager is pnpm; do not run npm or yarn. Commands: - `pnpm dev` starts the app on port 3000 - `pnpm test` runs Vitest once - `pnpm lint` and `pnpm typecheck` must both pass before a commit - `pnpm prisma migrate dev` after any change to `prisma/schema.prisma` Layout: - `src/app` holds routes and layouts only; no business logic - `src/lib` holds shared logic; `src/lib/db` is the only place Prisma is imported - `src/__tests__` mirrors `src/lib` Never edit `src/generated`; it comes from `pnpm codegen`. ``` `app-router.mdc` attaches when a file under `src/app` is in context, and encodes the distinction Next.js projects get wrong most often: ```markdown --- globs: src/app/**/*.tsx alwaysApply: false --- - Layouts and pages are Server Components by default. Add `'use client'` at the top of a file only when it needs state, event handlers, effects or browser APIs, and keep those files as leaves of the tree. - Fetch data in Server Components and pass it down as serialisable props. Never import `src/lib/db` from a file that starts with `'use client'`. - Follow @src/app/(dashboard)/projects/page.tsx as the reference page shape. ``` `api-routes.mdc` has the same shape under `globs: src/app/api/**/route.ts`: named `GET` and `POST` exports, validation through `src/lib/schemas`, and `@src/app/api/tasks/route.ts` as the reference handler. `testing.mdc` has a description and no globs, so the agent pulls it in when a request is about tests: ```markdown --- description: How tests are written and run in this repository alwaysApply: false --- - Vitest, no Jest globals; import `describe`, `it`, `expect` from `vitest`. - Tests live in `src/__tests__` and mirror the path of the module under test. - Database tests use the helper in @src/__tests__/helpers/db.ts; never hit the development database directly. ``` That is the whole set. Cursor rules for TypeScript projects rarely need more than this; when one grows past a screen, the fix is a glob-scoped split, not more prose. ## The best .cursorrules for Next.js is a directory, but here is the flat version Some teams keep one file because other tools read it. For them, a .cursorrules template is the same content with the scoping collapsed: ```text Acme dashboard: Next.js App Router, TypeScript strict, Prisma, Vitest. pnpm only. Commands: pnpm dev, pnpm test, pnpm lint, pnpm typecheck. src/app is routes only; src/lib/db is the only place Prisma is imported. Files under src/app are Server Components unless they start with 'use client'. Client components are leaves and never import src/lib/db. Route handlers export named GET/POST and validate with src/lib/schemas. Tests use Vitest in src/__tests__, mirroring src/lib. ``` The cost is visible: every request carries the testing and route-handler lines whether they apply or not, and nothing points at a reference file. The best .cursorrules for Next.js is the one you convert into the directory above. ## A shorter .cursorrules for React A plain React project without a framework needs less. A single-file .cursorrules for React looks like this: ```text Acme widgets: React with Vite and TypeScript. pnpm only. Commands: pnpm dev, pnpm test (Vitest and Testing Library), pnpm lint. - Function components with named exports; one component per file. - Co-locate Component.module.css and Component.test.tsx with the component. - State read by more than one component lives in a store under src/state. - Do not add a UI library; use the primitives in src/components/ui. ``` As `.cursor/rules/react.mdc` it is the same body under `globs: src/**/*.tsx` with `alwaysApply: false`, so it attaches only to component files. Both .cursorrules examples above say what the compiler and the linter cannot enforce, and nothing else. Strictness belongs in `tsconfig.json`, import order in the lint config; a rule restating either is dead weight in the context window. ## Generate the inventory from the repository, write the judgement calls by hand Cursor's rules reference lists two ways to create a rule in the editor: open Customize in the sidebar, go to Rules and click Add Rule; or type `/create-rule` in Agent and describe what you want, and the agent writes the file into `.cursor/rules` with its frontmatter ([cursor.com/docs/rules](https://cursor.com/docs/rules)). The help page documents a third and leads with it: open the command palette with Cmd/Ctrl+Shift+P, type "New Cursor Rule", then pick the rule type from the dropdown ([cursor.com/help/customization/rules](https://cursor.com/help/customization/rules)). None of the three generate .cursorrules — what each produces is an `.mdc` file under `.cursor/rules` — and neither page documents a command called "Generate Cursor Rules"; if a tutorial tells you to run one, check its date. Whichever route you take, the split between generated and hand-written content is the same: | Generate from the repository | Write by hand | |---|---| | Stack, package manager, runtime version | Directories the agent must not touch, and why | | Commands, from `package.json` scripts | The mistake that prompted the rule | | Directory layout | Boundaries: what may import what | | The canonical example file for each pattern | Anything that starts with "never" | Generated inventory drifts as soon as the repository changes, so keep it short and point at files with `@` rather than copying them. Hand-written rules are the ones with a reason attached: start simple, and add a rule when the agent makes the same mistake twice. ## What the Autoplans VS Code extension does with .cursorrules The [Autoplans VS Code extension](/vscode) has a command, **Autoplans: Generate Copilot Configuration**, also on the context menu of a project in its sidebar. It writes two files into the workspace: `.github/copilot-instructions.md` and `.github/chatmodes/Autoplanner.chatmode.md`, overwriting both on each run. Before writing the instructions file it asks the editor's language-model API for a chat model in the `gpt-4o` family. When one is available, it reads whichever of these exist in the workspace root, taking the first 2,000 characters of each: `.github/copilot-instructions.md`, `AGENT.md`, `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, `.windsurfrules`, `.clinerules` and `README.md`. That text goes to the model with the project name and description and an instruction to merge the conventions into a new `copilot-instructions.md`. When no model is available or the request fails, a static template is written instead and none of those files are read. So `.cursorrules` is an input, never an output. The command does not write `.cursorrules`, does not read or write `.cursor/rules/*.mdc`, does not modify `AGENTS.md` or `CLAUDE.md`, and does not push anything to GitHub — **Autoplans: Sync Project to Repository** offers a GitHub option, but it is marked coming soon and only the local write runs. If your Cursor rules are the canonical source, the extension gives you a Copilot file derived from them; the [copilot-instructions.md guide](/guides/copilot-instructions) covers the rest. ## Keeping the rules from going stale A wrong rule is worse than no rule: the agent follows it with the same confidence as a right one. [Context rot](/guides/context-rot) covers the general failure; for Cursor rules, these practices hold. - Keep the always-on rule to the facts that change least: stack, commands, layout. Anything volatile goes in a glob-scoped rule, where an error reaches only the files it matches. - Reference files instead of quoting them. `@src/app/api/tasks/route.ts` stays correct when the file changes; a pasted copy does not. - Only state commands that CI runs. If `pnpm typecheck` is in the rule and in the pipeline, the rule cannot silently outlive the script. - Delete rules whose reason is gone. Once the agent stops making the mistake, the rule is noise. - Keep task state out of rules. "We are migrating to the App Router" is true for a month and wrong for years; a [backlog](/guides/ai-coding-agent-backlog) is where that belongs. - Review the directory when the stack moves. A framework major or a rename should come with a diff under `.cursor/rules`. ## Questions that come up ### Does Cursor still read a .cursorrules file? Cursor's help documentation calls the root `.cursorrules` file legacy and says it will be deprecated, and its migration steps describe the old behaviour as the equivalent of an Always Apply rule ([cursor.com/help/customization/rules](https://cursor.com/help/customization/rules)). It does not say from which release a build stops reading the file. Recreate the content as an Always Apply rule under `.cursor/rules` and delete the flat file. ### Do the .cursorrules examples published online work as .mdc files? The bodies do. Add frontmatter, decide whether the rule is always-on or glob-scoped, and drop anything that restates your linter. Most published files are far longer than a project needs. ### Should I keep .cursorrules alongside .cursor/rules? Only if another tool reads the flat file. Make `.cursor/rules` the source and treat `.cursorrules` as an export, or the two disagree within a sprint.