Why Next.js App Router Needs Dedicated Rules
Next.js 13 introduced the App Router — a fundamentally different architecture from the Pages Router. React Server Components are the default. Data fetching happens in server components, not getServerSideProps. Layouts are nested and persistent. Metadata uses the generateMetadata API, not next/head. AI assistants trained on years of Pages Router code generate the wrong patterns in App Router projects.
The most common AI failures: using 'use client' on every component (defeating RSC), using getServerSideProps or getStaticProps (removed in App Router), using next/head for metadata (replaced by generateMetadata), creating API routes in pages/api/ instead of app/api/, and ignoring the layout/page/loading/error file conventions.
This is the most impactful framework-specific CLAUDE.md you can write. Next.js is the most popular React framework, and the App Router transition is where AI output quality diverges most from developer expectations.
Rule 1: React Server Components by Default
The rule: 'All components are React Server Components by default. Only add "use client" when the component needs: useState, useEffect, useRef, event handlers (onClick, onChange), browser-only APIs (window, document), or third-party client libraries. Never add "use client" preemptively — start as server, convert to client only when the compiler tells you to.'
For data fetching: 'Fetch data in server components using async/await: export default async function Page() { const data = await db.select().from(users); }. Never use useEffect for initial data loading. Never use SWR or React Query for data that can be fetched on the server. Client-side fetching is only for user-initiated actions (search, infinite scroll).'
For the boundary: 'Push "use client" as deep as possible. If a page needs one interactive button, don't make the whole page a client component. Extract the button into a separate client component and keep the rest as server. The more code runs on the server, the less JavaScript ships to the browser.'
- Server Component by default — "use client" only when hooks/events needed
- async/await in server components for data — no useEffect for initial loads
- Push "use client" deep — extract interactive parts into small client components
- Server: zero JS shipped, direct DB/API access, full Node.js capabilities
- Client: interactivity, browser APIs, state management, event handlers
If a page needs one interactive button, don't make the whole page a client component. Extract the button into a tiny "use client" component. The more code stays on the server, the less JS ships to the browser.
Rule 2: File Conventions and Routing
The rule: 'Follow App Router file conventions strictly: page.tsx for route pages, layout.tsx for shared layouts, loading.tsx for Suspense loading states, error.tsx for error boundaries, not-found.tsx for 404 handling. Route groups use (groupName) parentheses. Dynamic routes use [param] brackets. Catch-all routes use [...slug]. Parallel routes use @slot.'
For layouts: 'Layouts are persistent across navigation — use them for shared UI (nav, sidebar, footer). Never put data fetching that should re-run per page in a layout. Use templates (template.tsx) instead of layouts when you need re-mounting on navigation. Root layout must include <html> and <body> tags.'
For route handlers: 'API routes live in app/api/[route]/route.ts. Export named functions matching HTTP methods: export async function GET(request: Request), export async function POST(request: Request). Never use pages/api/ — that's Pages Router. Use NextResponse for responses.'
Rule 3: Server Actions for Mutations
The rule: 'Use server actions for all data mutations (create, update, delete). Define actions with "use server" at the function level or file level. Actions can be called from client components via form actions or from server components directly. Use revalidatePath or revalidateTag after mutations to update cached data. Never create API routes just for form submissions — use server actions.'
For forms: 'Use the action prop on <form> elements: <form action={createUser}>. Use useFormStatus for pending states. Use useActionState (React 19) for form state management. Validate input in the server action with Zod before processing. Return error state from the action — never throw to the client.'
For progressive enhancement: 'Server actions work without JavaScript. Forms submit as standard POST requests when JS is disabled. This makes your app resilient — form submissions never fail due to JS bundle loading issues. Always design with progressive enhancement in mind.'
- "use server" for all mutations — no API routes for form submissions
- revalidatePath/revalidateTag after mutations — keep cache fresh
- useFormStatus for pending states — useActionState for form state
- Zod validation in server actions — return errors, don't throw
- Progressive enhancement: forms work without JS by design
Server actions work without JavaScript — forms submit as standard POST requests. This makes your app resilient: form submissions never fail due to JS bundle loading issues. Design with this in mind.
Rule 4: Metadata and SEO
The rule: 'Use the Metadata API for all SEO: export const metadata: Metadata = { title, description, openGraph, twitter } for static metadata. Use export async function generateMetadata({ params }) for dynamic metadata. Never use next/head or <Head> — those are Pages Router. Include: title (under 60 chars), description (150-160 chars), OpenGraph image, and canonical URL.'
For dynamic pages: 'generateMetadata receives the same params as the page component. Fetch the data you need for metadata (title from database, etc.) in generateMetadata — Next.js deduplicates fetch calls, so fetching in both generateMetadata and the page component doesn't cause double requests.'
For images: 'Use opengraph-image.tsx or twitter-image.tsx for dynamically generated social images. Use the ImageResponse API to generate images from JSX. Provide alt text for all OG images. Set image dimensions in the metadata: openGraph: { images: [{ url, width: 1200, height: 630, alt }] }.'
Rule 5: Streaming, Suspense, and Loading States
The rule: 'Use loading.tsx for route-level loading states — it automatically wraps the page in a Suspense boundary. Use <Suspense fallback={...}> for component-level streaming: wrap slow components individually so fast parts render immediately. Use React.cache() for request-scoped data deduplication. Never block the entire page on a slow data fetch — stream it.'
For streaming patterns: 'Fetch critical data (page title, layout) in the page component. Wrap non-critical data (comments, recommendations, analytics) in Suspense with skeleton fallbacks. This renders the page shell instantly and streams in the slower parts. Users see content immediately — not a blank screen for 2 seconds.'
For error handling: 'Use error.tsx for route-level error boundaries. Use ErrorBoundary components for finer-grained error isolation. error.tsx must be a client component ("use client") — error boundaries require client-side state. Include a retry button that calls reset() from the error component props.'
Wrap slow components in <Suspense> so fast parts render immediately. Users see the page shell instantly while comments and recommendations stream in. Never block the entire page on a slow fetch.
Complete Next.js App Router Rules Template
Consolidated rules for Next.js 14+ with App Router.
- React Server Components by default — "use client" only for hooks/events/browser APIs
- async/await in server components — no useEffect for initial data
- File conventions: page.tsx, layout.tsx, loading.tsx, error.tsx, not-found.tsx
- Server actions for mutations — revalidatePath after writes — Zod validation
- Metadata API: generateMetadata for dynamic — never next/head
- Streaming: Suspense for slow components — loading.tsx for route-level
- API routes in app/api/ — named function exports (GET, POST) — NextResponse
- Tailwind utility classes — next/image for images — next/link for navigation