Next.js + TypeScript AI Rules

AI coding rules for Next.js 15 App Router with TypeScript strict mode, named exports, server components by default, and Tailwind. Production-grade conventions for Cursor, Claude Code, and Copilot.

TypeScriptNext.js#typescript#nextjs#app-router#reactLast updated 2026-05-05
tune

Want to customize this rules file? Open the generator with this stack pre-loaded.

Open in generatorarrow_forward

Save at .cursor/rules/main.mdc

Next.js + TypeScript

Project context

This is a Next.js 15 App Router project written in TypeScript with strict mode. It's a production web application — favor stability and explicitness over cleverness. The codebase is shared across multiple developers and AI tools; consistency matters more than personal preference.

Stack

  • Runtime: Node.js 20+ (LTS)
  • Framework: Next.js 15 (App Router only — never Pages Router)
  • UI: React 19 (with Server Components by default)
  • Language: TypeScript 5+ in strict mode ("strict": true, "noUncheckedIndexedAccess": true)
  • Styling: Tailwind CSS 4 with semantic CSS custom properties
  • Package manager: pnpm (do not use npm or yarn)
  • Linting: ESLint with eslint-config-next
  • Formatting: Prettier (run automatically via the editor / pre-commit hook)
  • Testing: Vitest for unit tests, Playwright for e2e

Folder structure

src/
  app/             — App Router routes (pages, layouts, loading, error, route handlers)
  components/      — Reusable React components, organized by feature subdir
  lib/             — Utilities and integrations, organized by feature subdir
  data/            — Static data and config files (TypeScript const arrays preferred)
  content/         — Static markdown / MDX content
  hooks/           — Reusable client-side hooks (only when shared across components)
  styles/          — Global styles, theme tokens (most styling lives in Tailwind classes)
public/            — Static assets served at the root
tests/             — Test utilities and Playwright tests (unit tests live next to source)

Routes live under src/app/. Components specific to one feature live under src/components/<feature>/. Cross-cutting components live at the top of src/components/.

Code style

  • Functional components only — no class components
  • Named exports only — no default exports except where Next.js requires them (page.tsx, layout.tsx, error.tsx, loading.tsx, not-found.tsx, opengraph-image.tsx, route.ts)
  • File names: kebab-case for utilities, PascalCase.tsx for components
  • Imports: absolute paths via @/ alias, never relative paths that climb (../../../)
  • Group imports: external packages, then @/... internal, then relative
  • Prefer const over let; never var
  • Prefer arrow functions for callbacks; named function declarations for top-level helpers
  • Type everything. Use unknown over any. If you reach for any, find another way

Patterns to follow

Server Components by default. Add "use client" only when the component needs state, effects, browser APIs, or event handlers. Most data fetching belongs in server components.

Async server components for data fetching.

export default async function Page() {
  const data = await fetchSomething()
  return <ClientUI initialData={data} />
}

Server Actions for mutations. Prefer Server Actions over route handlers for form submissions and mutations. Place them in a co-located actions.ts file with 'use server' at the top. Validate inputs with Zod at the action boundary.

Route Handlers for public APIs. Use route handlers (route.ts) only for public APIs (webhooks, RSS feeds, well-known endpoints). For internal mutations, use Server Actions.

Tailwind for styling. No CSS modules, no styled-components, no inline style props except for dynamic values that can't be expressed as classes. Use semantic color tokens (bg-surface, text-on-surface-variant) — never raw hex.

Composition over abstraction. Duplicate twice before extracting a component or hook. Premature abstraction is harder to undo than duplication.

Loading and error states are required. Every async route gets a loading.tsx. Every page gets an error.tsx boundary at its segment level.

Patterns to avoid

  • Pages Router patterns. No getServerSideProps, getStaticProps, getInitialProps. They don't exist in App Router.
  • useEffect for data fetching. Fetch in server components or use a Server Action. useEffect(() => fetch()) in client components leaks waterfalls and breaks SSR.
  • any. Use unknown and narrow.
  • Default exports. They make refactoring and grep harder.
  • Client components that don't need to be. Mark "use client" at the smallest leaf, not the layout root.
  • Inline server code in client components. import "server-only" at the top of any module that must never ship to the browser.
  • router.push for full navigations. Use <Link> for navigation; router.push is for programmatic navigation (after a form action, etc.).

Testing

  • Unit tests live alongside source: foo.tsfoo.test.ts
  • Use Vitest with @testing-library/react for component tests
  • Mock at the boundary — don't mock internal modules; mock fetch, fs, etc. at the system edge
  • Playwright tests live in tests/e2e/ and exercise critical user flows only
  • Run pnpm test before considering a task complete

Tooling

  • pnpm dev — Next.js dev server with Turbopack
  • pnpm build — production build
  • pnpm lint — ESLint
  • pnpm test — Vitest
  • pnpm e2e — Playwright
  • pnpm typechecktsc --noEmit

Run lint, typecheck, and test before considering any task done.

AI behavioral rules

  • Ask before installing dependencies. Surface the exact package and reason; let the human approve.
  • Run the linter and tests after editing. Don't claim a task is complete if either is failing.
  • Never modify migrations after they're committed. Generate a new migration instead.
  • Don't refactor unrelated code in the same change. One concern per change.
  • No comments unless they explain the why. Code names should describe what; only add comments for non-obvious motivation, hidden constraints, or workarounds.
  • No console.log in committed code. Remove debug logs before considering a task done.
  • Prefer editing existing files over creating new ones. Only create files when the new concern doesn't fit anywhere existing.
  • When in doubt, stop and ask. Better to ask one clarifying question than to ship a wrong implementation that needs a follow-up.

Frequently asked

How do I use this Next.js rules file with Cursor?

Pick "Cursor (.cursor/rules/*.mdc)" from the format dropdown above and click Copy. Save it at .cursor/rules/main.mdc in your project root and restart Cursor. The legacy .cursorrules format still works if you're on an older Cursor version — pick that option instead.

Can I use this with Claude Code (CLAUDE.md)?

Yes — pick "Claude Code (CLAUDE.md)" from the format dropdown above and copy. Save the file as CLAUDE.md at your repo root. Claude Code reads it automatically on every session. For monorepos, you can also drop nested CLAUDE.md files in subdirectories — Claude merges them when working in those paths.

Where exactly do I put this file?

It depends on the AI tool. Cursor reads .cursorrules or .cursor/rules/*.mdc at the project root. Claude reads CLAUDE.md at the project root. Copilot reads .github/copilot-instructions.md. The "Save at" path under each format in the dropdown shows the exact location for the format you picked.

Can I customize these Next.js rules for my project?

Yes — that's what the generator is for. Click "Open in generator" above and the wizard loads with this stack's defaults pre-selected. Toggle on or off the conventions you want, then re-export in your AI tool's format.

Will using this rules file slow down my AI tool?

No. Rules files count toward the model's context window but not toward latency in any noticeable way. The file is loaded once per session, not per token. The library files target 250–400 lines, well within every tool's recommended budget.

Should I commit this file to git?

Yes. The rules file is project documentation that benefits every developer using the AI tool. Commit it. The exception is personal-global settings (e.g. ~/.claude/CLAUDE.md) which are user-scoped and stay out of the repo.

Related stacks