| name | nextjs | 
| description | Guide for implementing Next.js - a React framework for production with server-side rendering, static generation, and modern web features. Use when building Next.js applications, implementing App Router, working with server components, data fetching, routing, or optimizing performance. | 
| license | MIT | 
| version | 1.0.0 | 
Next.js Skill
Next.js is a React framework for building full-stack web applications with server-side rendering, static generation, and powerful optimization features built-in.
Reference
https://nextjs.org/docs/llms.txt
When to Use This Skill
Use this skill when:
- Building new Next.js applications (v15+)
 - Implementing App Router architecture
 - Working with Server Components and Client Components
 - Setting up routing, layouts, and navigation
 - Implementing data fetching patterns
 - Optimizing images, fonts, and performance
 - Configuring metadata and SEO
 - Setting up API routes and route handlers
 - Migrating from Pages Router to App Router
 - Deploying Next.js applications
 
Core Concepts
App Router vs Pages Router
App Router (Recommended for v13+):
- Modern architecture with React Server Components
 - File-system based routing in 
app/directory - Layouts, loading states, and error boundaries
 - Streaming and Suspense support
 - Nested routing with layouts
 
Pages Router (Legacy):
- Traditional page-based routing in 
pages/directory - Uses 
getStaticProps,getServerSideProps,getInitialProps - Still supported for existing projects
 
Key Architectural Principles
- Server Components by Default: Components in 
app/are Server Components unless marked with'use client' - File-based Routing: File system defines application routes
 - Nested Layouts: Share UI across routes with layouts
 - Progressive Enhancement: Works without JavaScript when possible
 - Automatic Optimization: Images, fonts, scripts auto-optimized
 
Installation & Setup
Create New Project
npx create-next-app@latest my-app
# or
yarn create next-app my-app
# or
pnpm create next-app my-app
# or
bun create next-app my-app
Interactive Setup Prompts:
- TypeScript? (Yes recommended)
 - ESLint? (Yes recommended)
 - Tailwind CSS? (Optional)
 src/directory? (Optional)- App Router? (Yes for new projects)
 - Import alias? (Default: @/*)
 
Manual Setup
npm install next@latest react@latest react-dom@latest
package.json scripts:
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  }
}
Project Structure
my-app/
├── app/                    # App Router (v13+)
│   ├── layout.tsx         # Root layout
│   ├── page.tsx           # Home page
│   ├── loading.tsx        # Loading UI
│   ├── error.tsx          # Error UI
│   ├── not-found.tsx      # 404 page
│   ├── global.css         # Global styles
│   └── [folder]/          # Route segments
├── public/                # Static assets
├── components/            # React components
├── lib/                   # Utility functions
├── next.config.js         # Next.js configuration
├── package.json
└── tsconfig.json
Routing
File Conventions
page.tsx- Page UI for routelayout.tsx- Shared UI for segment and childrenloading.tsx- Loading UI (wraps page in Suspense)error.tsx- Error UI (wraps page in Error Boundary)not-found.tsx- 404 UIroute.ts- API endpoint (Route Handler)template.tsx- Re-rendered layout UIdefault.tsx- Parallel route fallback
Basic Routing
Static Route:
app/
├── page.tsx              → /
├── about/
│   └── page.tsx         → /about
└── blog/
    └── page.tsx         → /blog
Dynamic Route:
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
  return <h1>Post: {params.slug}</h1>
}
Catch-all Route:
// app/shop/[...slug]/page.tsx
export default function Shop({ params }: { params: { slug: string[] } }) {
  return <h1>Category: {params.slug.join('/')}</h1>
}
Optional Catch-all:
// app/docs/[[...slug]]/page.tsx
// Matches /docs, /docs/a, /docs/a/b, etc.
Route Groups
Organize routes without affecting URL:
app/
├── (marketing)/          # Group without URL segment
│   ├── about/page.tsx   → /about
│   └── blog/page.tsx    → /blog
└── (shop)/
    ├── products/page.tsx → /products
    └── cart/page.tsx     → /cart
Parallel Routes
Render multiple pages in same layout:
app/
├── @team/               # Slot
│   └── page.tsx
├── @analytics/          # Slot
│   └── page.tsx
└── layout.tsx           # Uses both slots
// app/layout.tsx
export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode
  team: React.ReactNode
  analytics: React.ReactNode
}) {
  return (
    <>
      {children}
      {team}
      {analytics}
    </>
  )
}
Intercepting Routes
Intercept routes to show in modal:
app/
├── feed/
│   └── page.tsx
├── photo/
│   └── [id]/
│       └── page.tsx
└── (..)photo/           # Intercepts /photo/[id]
    └── [id]/
        └── page.tsx
Layouts
Root Layout (Required)
// app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
Nested Layouts
// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <section>
      <nav>Dashboard Nav</nav>
      {children}
    </section>
  )
}
Layouts are:
- Shared across multiple pages
 - Preserve state on navigation
 - Do not re-render on navigation
 - Can fetch data
 
Server and Client Components
Server Components (Default)
Components in app/ are Server Components by default:
// app/page.tsx (Server Component)
async function getData() {
  const res = await fetch('https://api.example.com/data')
  return res.json()
}
export default async function Page() {
  const data = await getData()
  return <div>{data.title}</div>
}
Benefits:
- Fetch data on server
 - Access backend resources directly
 - Keep sensitive data on server
 - Reduce client-side JavaScript
 - Improve initial page load
 
Limitations:
- Cannot use hooks (useState, useEffect)
 - Cannot use browser APIs
 - Cannot add event listeners
 
Client Components
Mark components with 'use client' directive:
// components/counter.tsx
'use client'
import { useState } from 'react'
export function Counter() {
  const [count, setCount] = useState(0)
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  )
}
Use Client Components for:
- Interactive UI (onClick, onChange)
 - State management (useState, useReducer)
 - Effects (useEffect, useLayoutEffect)
 - Browser APIs (localStorage, navigator)
 - Custom hooks
 - React class components
 
Composition Pattern
// app/page.tsx (Server Component)
import { ClientComponent } from './client-component'
export default function Page() {
  return (
    <div>
      <h1>Server-rendered content</h1>
      <ClientComponent />
    </div>
  )
}
Data Fetching
Server Component Data Fetching
// app/posts/page.tsx
async function getPosts() {
  const res = await fetch('https://api.example.com/posts', {
    next: { revalidate: 3600 } // Revalidate every hour
  })
  if (!res.ok) throw new Error('Failed to fetch')
  return res.json()
}
export default async function PostsPage() {
  const posts = await getPosts()
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
Caching Strategies
Force Cache (Default):
fetch('https://api.example.com/data', { cache: 'force-cache' })
No Store (Dynamic):
fetch('https://api.example.com/data', { cache: 'no-store' })
Revalidate:
fetch('https://api.example.com/data', {
  next: { revalidate: 3600 } // Seconds
})
Tag-based Revalidation:
fetch('https://api.example.com/data', {
  next: { tags: ['posts'] }
})
// Revalidate elsewhere:
import { revalidateTag } from 'next/cache'
revalidateTag('posts')
Parallel Data Fetching
async function getData() {
  const [posts, users] = await Promise.all([
    fetch('https://api.example.com/posts').then(r => r.json()),
    fetch('https://api.example.com/users').then(r => r.json()),
  ])
  return { posts, users }
}
Sequential Data Fetching
async function getData() {
  const post = await fetch(`https://api.example.com/posts/${id}`).then(r => r.json())
  const author = await fetch(`https://api.example.com/users/${post.authorId}`).then(r => r.json())
  return { post, author }
}
Route Handlers (API Routes)
Basic Route Handler
// app/api/hello/route.ts
export async function GET(request: Request) {
  return Response.json({ message: 'Hello' })
}
export async function POST(request: Request) {
  const body = await request.json()
  return Response.json({ received: body })
}
Dynamic Route Handler
// app/api/posts/[id]/route.ts
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const post = await getPost(params.id)
  return Response.json(post)
}
export async function DELETE(
  request: Request,
  { params }: { params: { id: string } }
) {
  await deletePost(params.id)
  return new Response(null, { status: 204 })
}
Request Helpers
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const id = searchParams.get('id')
  const cookies = request.headers.get('cookie')
  return Response.json({ id })
}
Response Types
// JSON
return Response.json({ data: 'value' })
// Text
return new Response('Hello', { headers: { 'Content-Type': 'text/plain' } })
// Redirect
return Response.redirect('https://example.com')
// Status codes
return new Response('Not Found', { status: 404 })
Navigation
Link Component
import Link from 'next/link'
export default function Page() {
  return (
    <>
      <Link href="/about">About</Link>
      <Link href="/blog/post-1">Post 1</Link>
      <Link href={{ pathname: '/blog/[slug]', query: { slug: 'post-1' } }}>
        Post 1 (alternative)
      </Link>
    </>
  )
}
useRouter Hook (Client)
'use client'
import { useRouter } from 'next/navigation'
export function NavigateButton() {
  const router = useRouter()
  return (
    <button onClick={() => router.push('/dashboard')}>
      Dashboard
    </button>
  )
}
Router Methods:
router.push(href)- Navigate to routerouter.replace(href)- Replace current historyrouter.refresh()- Refresh current routerouter.back()- Navigate backrouter.forward()- Navigate forwardrouter.prefetch(href)- Prefetch route
Programmatic Navigation (Server)
import { redirect } from 'next/navigation'
export default async function Page() {
  const session = await getSession()
  if (!session) {
    redirect('/login')
  }
  return <div>Protected content</div>
}
Metadata & SEO
Static Metadata
// app/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
  title: 'My Page',
  description: 'Page description',
  keywords: ['nextjs', 'react'],
  openGraph: {
    title: 'My Page',
    description: 'Page description',
    images: ['/og-image.jpg'],
  },
  twitter: {
    card: 'summary_large_image',
    title: 'My Page',
    description: 'Page description',
    images: ['/twitter-image.jpg'],
  },
}
export default function Page() {
  return <div>Content</div>
}
Dynamic Metadata
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug)
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
  }
}
Metadata Files
favicon.ico,icon.png,apple-icon.png- Faviconsopengraph-image.png,twitter-image.png- Social imagesrobots.txt- Robots filesitemap.xml- Sitemap
Image Optimization
Image Component
import Image from 'next/image'
export default function Page() {
  return (
    <>
      {/* Local image */}
      <Image
        src="/profile.png"
        alt="Profile"
        width={500}
        height={500}
      />
      {/* Remote image */}
      <Image
        src="https://example.com/image.jpg"
        alt="Remote"
        width={500}
        height={500}
      />
      {/* Responsive fill */}
      <div style={{ position: 'relative', width: '100%', height: '400px' }}>
        <Image
          src="/hero.jpg"
          alt="Hero"
          fill
          style={{ objectFit: 'cover' }}
        />
      </div>
      {/* Priority loading */}
      <Image
        src="/hero.jpg"
        alt="Hero"
        width={1200}
        height={600}
        priority
      />
    </>
  )
}
Image Props:
src- Image path (local or URL)alt- Alt text (required)width,height- Dimensions (required unless fill)fill- Fill parent containersizes- Responsive sizesquality- 1-100 (default 75)priority- Preload imageplaceholder- 'blur' | 'empty'blurDataURL- Data URL for blur
Remote Image Configuration
// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        pathname: '/images/**',
      },
    ],
  },
}
Font Optimization
Google Fonts
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
})
const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-roboto-mono',
})
export default function RootLayout({ children }) {
  return (
    <html lang="en" className={`${inter.className} ${robotoMono.variable}`}>
      <body>{children}</body>
    </html>
  )
}
Local Fonts
import localFont from 'next/font/local'
const myFont = localFont({
  src: './fonts/my-font.woff2',
  display: 'swap',
  variable: '--font-my-font',
})
Loading States
Loading File
// app/dashboard/loading.tsx
export default function Loading() {
  return <div>Loading dashboard...</div>
}
Streaming with Suspense
// app/page.tsx
import { Suspense } from 'react'
async function Posts() {
  const posts = await getPosts()
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}
export default function Page() {
  return (
    <div>
      <h1>My Posts</h1>
      <Suspense fallback={<div>Loading posts...</div>}>
        <Posts />
      </Suspense>
    </div>
  )
}
Error Handling
Error File
// app/error.tsx
'use client'
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Try again</button>
    </div>
  )
}
Global Error
// app/global-error.tsx
'use client'
export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  )
}
Not Found
// app/not-found.tsx
export default function NotFound() {
  return (
    <div>
      <h2>404 - Not Found</h2>
      <p>Could not find requested resource</p>
    </div>
  )
}
// Trigger programmatically
import { notFound } from 'next/navigation'
export default async function Page({ params }) {
  const post = await getPost(params.id)
  if (!post) {
    notFound()
  }
  return <div>{post.title}</div>
}
Middleware
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
  // Authentication check
  const token = request.cookies.get('token')
  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
  // Add custom header
  const response = NextResponse.next()
  response.headers.set('x-custom-header', 'value')
  return response
}
export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*'],
}
Environment Variables
# .env.local
DATABASE_URL=postgresql://...
NEXT_PUBLIC_API_URL=https://api.example.com
// Server-side only
const dbUrl = process.env.DATABASE_URL
// Client and server (NEXT_PUBLIC_ prefix)
const apiUrl = process.env.NEXT_PUBLIC_API_URL
Configuration
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // React strict mode
  reactStrictMode: true,
  // Image domains
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: 'example.com' },
    ],
  },
  // Redirects
  async redirects() {
    return [
      {
        source: '/old-page',
        destination: '/new-page',
        permanent: true,
      },
    ]
  },
  // Rewrites
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: 'https://api.example.com/:path*',
      },
    ]
  },
  // Headers
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          { key: 'X-Frame-Options', value: 'DENY' },
        ],
      },
    ]
  },
  // Environment variables
  env: {
    CUSTOM_KEY: 'value',
  },
}
module.exports = nextConfig
Best Practices
- Use Server Components: Default to Server Components, use Client Components only when needed
 - Optimize Images: Always use 
next/imagefor automatic optimization - Metadata: Set proper metadata for SEO
 - Loading States: Provide loading UI with Suspense
 - Error Handling: Implement error boundaries
 - Route Handlers: Use for API endpoints instead of separate backend
 - Caching: Leverage built-in caching strategies
 - Layouts: Use nested layouts to share UI
 - TypeScript: Enable TypeScript for type safety
 - Performance: Use 
priorityfor above-fold images, lazy load below-fold 
Common Patterns
Protected Routes
// app/dashboard/layout.tsx
import { redirect } from 'next/navigation'
import { getSession } from '@/lib/auth'
export default async function DashboardLayout({ children }) {
  const session = await getSession()
  if (!session) {
    redirect('/login')
  }
  return <>{children}</>
}
Data Mutations (Server Actions)
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
  const title = formData.get('title')
  await db.post.create({ data: { title } })
  revalidatePath('/posts')
}
// app/posts/new/page.tsx
import { createPost } from '@/app/actions'
export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" type="text" required />
      <button type="submit">Create</button>
    </form>
  )
}
Static Generation
// Generate static params for dynamic routes
export async function generateStaticParams() {
  const posts = await getPosts()
  return posts.map(post => ({
    slug: post.slug,
  }))
}
export default async function Post({ params }) {
  const post = await getPost(params.slug)
  return <article>{post.content}</article>
}
Deployment
Vercel (Recommended)
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel
Self-Hosting
# Build
npm run build
# Start production server
npm start
Requirements:
- Node.js 18.17 or later
 output: 'standalone'in next.config.js (optional, reduces size)
Docker
FROM node:18-alpine AS base
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]
Troubleshooting
Common Issues
Hydration errors
- Ensure server and client render same content
 - Check for browser-only code in Server Components
 - Verify no conditional rendering based on browser APIs
 
Images not loading
- Add remote domains to 
next.config.js - Check image paths (use leading 
/for public) - Verify width/height provided
 
- Add remote domains to 
 API route 404
- Check file is named 
route.ts/jsnotindex.ts - Verify export named GET/POST not default export
 - Ensure in 
app/api/directory 
- Check file is named 
 "use client" errors
- Add 
'use client'to components using hooks - Import Client Components in Server Components, not vice versa
 - Check event handlers have 
'use client' 
- Add 
 Metadata not updating
- Clear browser cache
 - Check metadata export is named correctly
 - Verify async generateMetadata returns Promise
 
Resources
- Documentation: https://nextjs.org/docs
 - Learn Course: https://nextjs.org/learn
 - Examples: https://github.com/vercel/next.js/tree/canary/examples
 - Blog: https://nextjs.org/blog
 - GitHub: https://github.com/vercel/next.js
 
Implementation Checklist
When building with Next.js:
-  Create project with 
create-next-app - Configure TypeScript and ESLint
 - Set up root layout with metadata
 - Implement routing structure
 - Add loading and error states
 - Configure image optimization
 - Set up font optimization
 - Implement data fetching patterns
 - Add API routes as needed
 - Configure environment variables
 - Set up middleware if needed
 - Optimize for production build
 - Test in production mode
 - Configure deployment platform
 - Set up monitoring and analytics