---
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
1. **Server Components by Default**: Components in `app/` are Server Components unless marked with `'use client'`
2. **File-based Routing**: File system defines application routes
3. **Nested Layouts**: Share UI across routes with layouts
4. **Progressive Enhancement**: Works without JavaScript when possible
5. **Automatic Optimization**: Images, fonts, scripts auto-optimized
## Installation & Setup
### Create New Project
```bash
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
```bash
npm install next@latest react@latest react-dom@latest
```
**package.json scripts:**
```json
{
"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 route
- `layout.tsx` - Shared UI for segment and children
- `loading.tsx` - Loading UI (wraps page in Suspense)
- `error.tsx` - Error UI (wraps page in Error Boundary)
- `not-found.tsx` - 404 UI
- `route.ts` - API endpoint (Route Handler)
- `template.tsx` - Re-rendered layout UI
- `default.tsx` - Parallel route fallback
### Basic Routing
**Static Route:**
```
app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
└── blog/
└── page.tsx → /blog
```
**Dynamic Route:**
```tsx
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return
Post: {params.slug}
;
}
```
**Catch-all Route:**
```tsx
// app/shop/[...slug]/page.tsx
export default function Shop({ params }: { params: { slug: string[] } }) {
return Category: {params.slug.join("/")}
;
}
```
**Optional Catch-all:**
```tsx
// 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
```
```tsx
// 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)
```tsx
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
### Nested Layouts
```tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
);
}
```
**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:
```tsx
// 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 {data.title}
;
}
```
**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:
```tsx
// components/counter.tsx
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return ;
}
```
**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
```tsx
// app/page.tsx (Server Component)
import { ClientComponent } from "./client-component";
export default function Page() {
return (
Server-rendered content
);
}
```
## Data Fetching
### Server Component Data Fetching
```tsx
// 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 (
{posts.map((post) => (
- {post.title}
))}
);
}
```
### Caching Strategies
**Force Cache (Default):**
```tsx
fetch("https://api.example.com/data", { cache: "force-cache" });
```
**No Store (Dynamic):**
```tsx
fetch("https://api.example.com/data", { cache: "no-store" });
```
**Revalidate:**
```tsx
fetch("https://api.example.com/data", {
next: { revalidate: 3600 }, // Seconds
});
```
**Tag-based Revalidation:**
```tsx
fetch("https://api.example.com/data", {
next: { tags: ["posts"] },
});
// Revalidate elsewhere:
import { revalidateTag } from "next/cache";
revalidateTag("posts");
```
### Parallel Data Fetching
```tsx
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
```tsx
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
```tsx
// 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
```tsx
// 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
```tsx
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
```tsx
// 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
```tsx
import Link from "next/link";
export default function Page() {
return (
<>
About
Post 1
Post 1 (alternative)
>
);
}
```
### useRouter Hook (Client)
```tsx
"use client";
import { useRouter } from "next/navigation";
export function NavigateButton() {
const router = useRouter();
return ;
}
```
**Router Methods:**
- `router.push(href)` - Navigate to route
- `router.replace(href)` - Replace current history
- `router.refresh()` - Refresh current route
- `router.back()` - Navigate back
- `router.forward()` - Navigate forward
- `router.prefetch(href)` - Prefetch route
### Programmatic Navigation (Server)
```tsx
import { redirect } from "next/navigation";
export default async function Page() {
const session = await getSession();
if (!session) {
redirect("/login");
}
return Protected content
;
}
```
## Metadata & SEO
### Static Metadata
```tsx
// 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 Content
;
}
```
### Dynamic Metadata
```tsx
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }): Promise {
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` - Favicons
- `opengraph-image.png`, `twitter-image.png` - Social images
- `robots.txt` - Robots file
- `sitemap.xml` - Sitemap
## Image Optimization
### Image Component
```tsx
import Image from "next/image";
export default function Page() {
return (
<>
{/* Local image */}
{/* Remote image */}
{/* Responsive fill */}
{/* Priority loading */}
>
);
}
```
**Image Props:**
- `src` - Image path (local or URL)
- `alt` - Alt text (required)
- `width`, `height` - Dimensions (required unless fill)
- `fill` - Fill parent container
- `sizes` - Responsive sizes
- `quality` - 1-100 (default 75)
- `priority` - Preload image
- `placeholder` - 'blur-sm' | 'empty'
- `blurDataURL` - Data URL for blur
### Remote Image Configuration
```js
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "example.com",
pathname: "/images/**",
},
],
},
};
```
## Font Optimization
### Google Fonts
```tsx
// 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 (
{children}
);
}
```
### Local Fonts
```tsx
import localFont from "next/font/local";
const myFont = localFont({
src: "./fonts/my-font.woff2",
display: "swap",
variable: "--font-my-font",
});
```
## Loading States
### Loading File
```tsx
// app/dashboard/loading.tsx
export default function Loading() {
return Loading dashboard...
;
}
```
### Streaming with Suspense
```tsx
// app/page.tsx
import { Suspense } from "react";
async function Posts() {
const posts = await getPosts();
return (
{posts.map((p) => (
- {p.title}
))}
);
}
export default function Page() {
return (
My Posts
Loading posts...}>
);
}
```
## Error Handling
### Error File
```tsx
// app/error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
Something went wrong!
{error.message}
);
}
```
### Global Error
```tsx
// app/global-error.tsx
"use client";
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
Something went wrong!
);
}
```
### Not Found
```tsx
// app/not-found.tsx
export default function NotFound() {
return (
404 - Not Found
Could not find requested resource
);
}
// Trigger programmatically
import { notFound } from "next/navigation";
export default async function Page({ params }) {
const post = await getPost(params.id);
if (!post) {
notFound();
}
return {post.title}
;
}
```
## Middleware
```tsx
// 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
# .env.local
DATABASE_URL=postgresql://...
NEXT_PUBLIC_API_URL=https://api.example.com
```
```tsx
// 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
```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
1. **Use Server Components**: Default to Server Components, use Client Components only when needed
2. **Optimize Images**: Always use `next/image` for automatic optimization
3. **Metadata**: Set proper metadata for SEO
4. **Loading States**: Provide loading UI with Suspense
5. **Error Handling**: Implement error boundaries
6. **Route Handlers**: Use for API endpoints instead of separate backend
7. **Caching**: Leverage built-in caching strategies
8. **Layouts**: Use nested layouts to share UI
9. **TypeScript**: Enable TypeScript for type safety
10. **Performance**: Use `priority` for above-fold images, lazy load below-fold
## Common Patterns
### Protected Routes
```tsx
// 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)
```tsx
// 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 (
);
}
```
### Static Generation
```tsx
// 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 {post.content};
}
```
## Deployment
### Vercel (Recommended)
```bash
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel
```
### Self-Hosting
```bash
# 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
```dockerfile
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
1. **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
2. **Images not loading**
- Add remote domains to `next.config.js`
- Check image paths (use leading `/` for public)
- Verify width/height provided
3. **API route 404**
- Check file is named `route.ts/js` not `index.ts`
- Verify export named GET/POST not default export
- Ensure in `app/api/` directory
4. **"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'`
5. **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