Next.js App Router AI Rules

Opinionated rules for Next.js App Router projects: when to mark "use client", how to do data fetching in RSC, route handlers vs server actions, and metadata conventions. Built for AI assistants that keep mixing in Pages Router patterns.

TypeScriptNext.js#nextjs#app-router#rsc#server-actionsLast 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 App Router

Project context

This is a Next.js 15 App Router project. The codebase uses Server Components, Server Actions, and the file-based App Router conventions exclusively. AI assistants frequently confuse App Router with Pages Router — these rules exist to prevent that.

Stack

  • Next.js 15 (App Router)
  • React 19 (Server Components first)
  • TypeScript strict mode
  • Tailwind 4 for styling
  • pnpm as the package manager

Folder layout

src/app/
  layout.tsx              — root layout (every route inherits)
  page.tsx                — root page
  (marketing)/            — route group, doesn't affect URL
  blog/[slug]/page.tsx    — dynamic route
  api/route.ts            — only for public APIs and webhooks
  loading.tsx             — Suspense fallback for the segment
  error.tsx               — error boundary for the segment
  not-found.tsx           — 404 for the segment

Co-locate route-specific helpers next to the page (actions.ts, schema.ts, components folder). Use route groups (name)/ to organize routes without affecting URLs.

Server Components vs Client Components

Default to Server Components. They render on the server, ship zero JS, and can await directly.

export default async function Page() {
  const data = await getData()
  return <Content data={data} />
}

Add "use client" only when needed:

  • Component uses useState, useEffect, useRef
  • Component handles browser events (onClick, onChange)
  • Component uses browser-only APIs (window, localStorage, navigator)
  • Component uses third-party libraries that depend on the above

Push the client boundary as deep as possible. A page can be a server component that renders a small interactive <Toggle /> client component. Don't mark the whole page client just to have one button.

Never import 'server-only' from a file that gets pulled into a client component. The error message is opaque; check the import graph if you see "ReactServerComponentsError".

Data fetching

  • Fetch in server components with await
  • Use fetch() directly — Next.js automatically dedupes and caches per-request
  • Configure caching explicitly: fetch(url, { next: { revalidate: 60 } }) or { cache: 'no-store' }
  • For database queries, call your DB client directly from a server component
  • Pass server-fetched data as props to client components — never re-fetch in a useEffect

Server Actions

Use Server Actions for form submissions and mutations.

// app/posts/new/actions.ts
'use server'

import { z } from 'zod'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'

const Schema = z.object({ title: z.string().min(1).max(200) })

export async function createPost(formData: FormData) {
  const parsed = Schema.parse({ title: formData.get('title') })
  await db.posts.create({ data: parsed })
  revalidatePath('/posts')
  redirect('/posts')
}
  • Validate input with Zod at the action boundary
  • Call revalidatePath() or revalidateTag() after mutations
  • Use redirect() to navigate after success

Route Handlers

Route handlers (route.ts) are for public APIs only:

  • Webhooks (Stripe, Clerk, etc.)
  • Public REST endpoints consumed by external clients
  • Well-known files (robots.txt, sitemap.xml)

Don't use route handlers as a private internal API for your own client components — use Server Actions instead.

Metadata & SEO

  • Export metadata from page.tsx and layout.tsx
  • For dynamic routes, export generateMetadata (async)
  • Use alternates: { canonical: ... } to declare canonical URLs
  • For OG images, use opengraph-image.tsx (or .png / .jpg) co-located with the route
  • Never set <title> or <meta> tags manually in JSX — use the metadata API

Caching

Three caches in App Router:

  1. Request memoization — within one request, identical fetch() calls are deduped
  2. Data Cache — persistent across requests; controlled via fetch options
  3. Full Route Cache — static rendering output

Be deliberate. If a page must be dynamic, opt in via export const dynamic = 'force-dynamic' or use cookies() / headers().

Patterns to avoid

  • getServerSideProps, getStaticProps, getInitialProps — Pages Router only
  • _app.tsx, _document.tsx — replaced by layout.tsx
  • next/router — use next/navigation instead (useRouter, usePathname, useSearchParams)
  • <Head> from next/head — use the metadata API
  • Fetching in useEffect — use server components or actions
  • 'use client' at the layout root — push it down to the leaf

Testing

  • Unit tests with Vitest, alongside source files
  • E2E with Playwright for critical paths only
  • Mock at the boundary (mock fetch, not internal helpers)
  • Run pnpm test && pnpm typecheck && pnpm lint before claiming done

Tooling

  • pnpm dev — dev server (uses Turbopack on 15+)
  • pnpm build — production build
  • pnpm lint — ESLint with eslint-config-next
  • pnpm typechecktsc --noEmit

AI behavioral rules

  • Confirm App Router before suggesting any Next.js feature; assume App Router unless told otherwise
  • Never suggest getServerSideProps or getStaticProps — they don't exist here
  • When fetching data, default to a server component
  • When adding a mutation, default to a Server Action
  • Push "use client" to the smallest leaf component
  • Run lint, typecheck, and tests before declaring a task complete
  • Don't add a feature flag or // removed comment when deleting code — just delete it

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