nextjs
About
This Claude Skill provides comprehensive guidance for implementing Next.js applications using modern App Router architecture. It covers key features like Server Components, data fetching, routing, and performance optimization for Next.js v15+. Use this skill when building full-stack applications that require server-side rendering, static generation, or migration from Pages Router.
Documentation
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<Metadata>
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
Quick Install
/plugin add https://github.com/Elios-FPT/EliosCodePracticeService/tree/main/nextjsCopy and paste this command in Claude Code to install this skill
GitHub 仓库
Related Skills
langchain
MetaLangChain is a framework for building LLM applications using agents, chains, and RAG pipelines. It supports multiple LLM providers, offers 500+ integrations, and includes features like tool calling and memory management. Use it for rapid prototyping and deploying production systems like chatbots, autonomous agents, and question-answering services.
llamaindex
MetaLlamaIndex is a data framework for building RAG-powered LLM applications, specializing in document ingestion, indexing, and querying. It provides key features like vector indices, query engines, and agents, and supports over 300 data connectors. Use it for document Q&A, chatbots, and knowledge retrieval when building data-centric applications.
business-rule-documentation
MetaThis skill provides standardized templates for systematically documenting business logic and domain knowledge following Domain-Driven Design principles. It helps developers capture business rules, process flows, decision trees, and terminology glossaries to maintain consistency between requirements and implementation. Use it when documenting domain models, creating business rule repositories, or bridging communication between business and technical teams.
project-structure
MetaThis skill provides comprehensive project structure guidelines and best practices for organizing codebases across various project types. It offers standardized directory patterns for monorepos, web frameworks, backend services, and libraries to ensure scalable, maintainable architecture. Use it when designing new project structures, organizing monorepo workspaces, or establishing code organization conventions for teams.
