--- name: nextjs description: | Next.js 14+ framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Next.js projects, or when the user mentions Next.js/Next. Provides App Router patterns, server components, data fetching, and deployment guidelines. license: MIT metadata: author: samuel version: "1.0" category: framework language: typescript extensions: ".jsx,.tsx" --- # Next.js Framework Guide > **Framework**: Next.js 14+ (App Router) > **Language**: TypeScript/JavaScript > **Use Cases**: Full-Stack Web Apps, SSR/SSG, E-commerce, Blogs, Dashboards ## Overview Next.js is a React framework providing server-side rendering, static site generation, API routes, and full-stack development in a single codebase. Version 14+ uses the App Router as the default, built on React Server Components. ## Project Setup ```bash # Create new Next.js app npx create-next-app@latest my-app --typescript --tailwind --eslint --app cd my-app npm run dev ``` ### Recommended Project Structure ``` my-app/ ├── app/ │ ├── (auth)/ # Route group (no URL segment) │ │ ├── login/page.tsx │ │ └── register/page.tsx │ ├── dashboard/ │ │ ├── page.tsx # /dashboard │ │ ├── loading.tsx # Loading UI │ │ ├── error.tsx # Error boundary │ │ └── layout.tsx # Dashboard layout │ ├── api/ │ │ └── users/route.ts # API route handler │ ├── globals.css │ ├── layout.tsx # Root layout (required) │ └── page.tsx # Home page (/) ├── components/ │ ├── ui/ # Reusable UI components │ └── features/ # Feature-specific components ├── lib/ │ ├── db.ts # Database client │ └── utils.ts # Utility functions ├── hooks/ # Custom React hooks ├── types/ # TypeScript type definitions ├── public/ # Static assets ├── middleware.ts # Edge middleware ├── next.config.js ├── tailwind.config.ts └── package.json ``` ## Routing (App Router) ### File-Based Routing Conventions | File | Purpose | |-----------------|----------------------------------------------| | `page.tsx` | Route UI (makes segment publicly accessible) | | `layout.tsx` | Shared layout (wraps children, persists) | | `loading.tsx` | Loading UI (Suspense boundary) | | `error.tsx` | Error boundary (must be `'use client'`) | | `not-found.tsx` | 404 UI for this segment | | `route.ts` | API route handler (GET, POST, etc.) | | `template.tsx` | Like layout but re-mounts on navigation | | `default.tsx` | Fallback for parallel routes | ### Route Patterns ``` app/ ├── page.tsx # / ├── about/page.tsx # /about ├── blog/ │ ├── page.tsx # /blog │ └── [slug]/page.tsx # /blog/:slug (dynamic) ├── shop/ │ └── [...categories]/page.tsx # /shop/a/b/c (catch-all) ├── (marketing)/ # Route group (no URL impact) │ ├── pricing/page.tsx # /pricing │ └── features/page.tsx # /features └── @modal/ # Parallel route (named slot) └── login/page.tsx ``` ### Page Component with Params ```tsx // app/blog/[slug]/page.tsx interface PageProps { params: { slug: string }; searchParams: { [key: string]: string | string[] | undefined }; } export default function BlogPost({ params, searchParams }: PageProps) { return

Post: {params.slug}

; } export async function generateMetadata({ params }: PageProps): Promise { const post = await getPost(params.slug); return { title: post.title, description: post.excerpt }; } ``` ### Layouts ```tsx // app/layout.tsx -- Root Layout (required, wraps entire app) import { Inter } from 'next/font/google'; const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { title: { default: 'My App', template: '%s | My App' }, description: 'My application', }; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Nested layouts compose automatically. Dashboard layout wraps all `/dashboard/*` routes: ```tsx // app/dashboard/layout.tsx export default function DashboardLayout({ children }: { children: React.ReactNode }) { return (
{children}
); } ``` ## Server Components vs Client Components ### Decision Rule | Need | Component Type | |-------------------------------------------|---------------| | Fetch data, access backend resources | Server (default) | | Static rendering, SEO content | Server | | Use hooks (useState, useEffect, etc.) | Client | | Browser APIs (window, localStorage) | Client | | Event handlers (onClick, onChange) | Client | | Third-party client-only libraries | Client | ### Server Component (Default) All components in the `app/` directory are Server Components by default. They run on the server only and can directly access databases, file systems, and secrets. ```tsx // app/users/page.tsx -- Server Component (no directive needed) import { db } from '@/lib/db'; export default async function UsersPage() { const users = await db.user.findMany(); return ( ); } ``` ### Client Component Add `'use client'` at the top of the file. Push this directive as low in the tree as possible. ```tsx // components/Counter.tsx 'use client'; import { useState } from 'react'; export function Counter() { const [count, setCount] = useState(0); return ; } ``` ### Composition Pattern Fetch data in Server Components, pass to Client Components as props: ```tsx // app/dashboard/page.tsx (Server Component) import { ClientSidebar } from '@/components/ClientSidebar'; import { db } from '@/lib/db'; export default async function Dashboard() { const stats = await db.stats.get(); return (
); } ``` ## Data Fetching ### Server Component Fetch with Caching ```tsx async function getProducts() { const res = await fetch('https://api.example.com/products', { next: { revalidate: 3600 }, // ISR: revalidate every hour }); if (!res.ok) throw new Error('Failed to fetch products'); return res.json(); } ``` ### Fetch Caching Options | Option | Behavior | |---------------------------------|-----------------------------------| | `{ cache: 'force-cache' }` | Static (default for GET) | | `{ cache: 'no-store' }` | Dynamic (no caching) | | `{ next: { revalidate: N } }` | ISR (revalidate every N seconds) | | `{ next: { tags: ['posts'] } }`| Tag-based revalidation | ### Parallel Fetching Always fetch independent data in parallel with `Promise.all`: ```tsx export default async function Dashboard({ params }: { params: { id: string } }) { const [user, orders] = await Promise.all([ getUser(params.id), getOrders(params.id), ]); return
; } ``` ### Streaming with Suspense ```tsx import { Suspense } from 'react'; export default function Dashboard() { return (
}> }>
); } ``` ## Server Actions Define mutations with `'use server'`. They run on the server and can be called from forms or client code. ```tsx // app/actions.ts 'use server'; import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import { z } from 'zod'; const createPostSchema = z.object({ title: z.string().min(1), content: z.string().min(10), }); export async function createPost(formData: FormData) { const validated = createPostSchema.parse({ title: formData.get('title'), content: formData.get('content'), }); await db.post.create({ data: validated }); revalidatePath('/posts'); redirect(`/posts`); } ``` Use in a form (no client JavaScript required for basic submissions): ```tsx export default function NewPost() { return (