Cursor rules for Next.js and TypeScript

How Cursor loads .mdc rules and AGENTS.md, a Next.js + TypeScript rule set for the App Router and a modular backend, and how to back it with lint.

MC
Martin Coll

The Cursor rules for Next.js and TypeScript that matter most are the ones about where code is allowed to live, and what it is allowed to import. Most "Cursor rules for Next.js" lists are style preferences — prefer server components, no any, named exports — which are useful, but mostly things a linter or the type checker already enforces. The rules that decide whether a codebase is still maintainable after six months of AI-written changes are the architectural ones, and this guide is about those.

Code for this guide: https://github.com/KonixDev/claude-code-guardrails — the open-source (MIT) claude-code-guardrails kit.

How Cursor loads rules (2026)

From Cursor's documentation:

  • Project rules live in .cursor/rules/ and must use the .mdc extension. A plain .md file in that folder is ignored by the rules system. Subfolders are allowed.
  • Each .mdc starts with frontmatter. Three fields decide when the rule is included:
alwaysApplydescriptionglobsWhen the rule is used
true——Always
false—setWhen a matching file is in context
falseset—When the agent judges it relevant from the description
false——Only when you @-mention it
  • Cursor also reads AGENTS.md at the project root and in subdirectories, as plain markdown. Nested files combine with their parents, with the more specific one taking precedence.
  • Rules apply in the order Team → Project → User, and the earlier source wins on a conflict. Cursor's own advice: keep each rule under 500 lines.

That last point changes how to organize rules: put the architecture in AGENTS.md, which Claude Code and Codex read too, and use .mdc files for Cursor-specific, file-scoped reminders.

A rule set for a modular Next.js + TypeScript SaaS

Assume the backend logic lives in src/modules/<context>/, each context split into domain/, application/, infrastructure/ and interfaces/, and the Next.js app consumes it.

AGENTS.md (excerpt — the rules every agent reads):

## Rules
1. Dependencies point inward. domain/ imports nothing outside itself;
   application/ imports only its own domain/.
2. No frameworks, ORMs or SDKs in domain/ or application/. Need I/O?
   Declare a port in application/, implement it in infrastructure/.
3. Across modules, only `import type`. Never call another module's
   internals at runtime.
4. Done means `npm run guardrails` passes. Never disable a rule to get green.

.cursor/rules/architecture.mdc (auto-attached when module code is in play):

---
description: Layered architecture rules for src/modules (domain, application, infrastructure, interfaces) that ESLint enforces. Apply when creating or moving code between modules or layers.
globs: src/modules/**
alwaysApply: false
---

The full rules live in AGENTS.md. In short:
- domain/ imports nothing outside itself; application/ only its own domain/.
- No frameworks, ORMs or SDKs in domain/ or application/.
- Across modules, only `import type`.
- These are enforced by eslint.boundaries.mjs. Never disable the rule;
  move the code or add a port.

.cursor/rules/nextjs.mdc (your app conventions, scoped to the App Router folder — use src/app/** or apps/web/app/** if that is where yours lives):

---
description: Next.js App Router and TypeScript conventions for this repo.
globs: app/**
alwaysApply: false
---

Components
- Server components by default. Add "use client" only for state, effects,
  event handlers or browser APIs, and push it to the smallest leaf component.
- Never import server-only code (database, secrets, SDKs with keys) into a
  client component. Mark such modules with `import 'server-only'`.

Data and mutations
- Route handlers and server actions are thin: validate input, call an
  application/ use case, map the result. No database calls in them.
- Validate every request body and action payload with the shared schemas from
  packages/contracts. Never re-declare a field on the client.
- After a mutation, revalidate what changed (`revalidatePath` /
  `revalidateTag`) instead of disabling caching globally.

TypeScript
- No `any`. Use `unknown` plus a type guard or a Zod parse at the boundary.
- Explicit return types on exported functions and server actions.
- Prefer named exports; default exports only where Next.js requires them
  (page, layout, route segments).

Before finishing
- Run `npm run guardrails` (lint + typecheck) and make it pass.

The "thin route handlers" block is what keeps database calls from spreading across app/: the agent has one obvious place for business logic, and lint rejects the shortcut.

Back the rules that must never break with lint

A rule file tells the agent why and where; it does not stop it (the longer argument). So every rule above that must never break also exists as a check:

// eslint.config.mjs
import guardrails from './eslint.boundaries.mjs';

export default [
  // ...your existing config
  ...guardrails(), // layers + type-only cross-module imports
];

Run it in the editor and in CI: the same config that explains the rule to a human reviewer fails the build when the agent breaks it. We walk through the exact rules in compiler-enforced DDD boundaries.

Checklist

  • Architecture rules in AGENTS.md, not duplicated per tool.
  • .mdc files scoped with globs instead of alwaysApply: true, so they don't crowd every request.
  • Each rule that must never break also exists as a lint rule or a CI check.
  • One command (npm run guardrails) the agent is told to run before it is done.

Get the files

The AGENTS.md, the architecture .mdc, the ESLint boundary config and a CI workflow are in claude-code-guardrails (MIT, copy-in). Cursor also has its own hooks (.cursor/hooks.json, with events such as afterFileEdit and stop); the kit's hook scripts are written for Claude Code — see Claude Code hooks to enforce your architecture.

FAQ

Is .cursorrules still a thing? Cursor's current rules documentation only describes .mdc files in .cursor/rules/ and AGENTS.md. New projects should use those.

Should rules be alwaysApply: true? Only short, truly global ones. Everything else is better scoped with globs, so it shows up when the matching files are in play and stays out of the way otherwise.

How long can a rule be? Cursor recommends keeping each rule under 500 lines. Shorter, globs-scoped rules plus a linter for the hard constraints beat one exhaustive document.


useDeploy is a Next.js + TypeScript SaaS base that ships AGENTS.md, this Cursor rule and the ESLint boundaries on top of auth, organizations and billing. See what's included.