Next.js Project Manager Skill
Overview
This skill helps you build production-ready Next.js 14+ applications using the App Router. Use this when working on routing, components, server actions, data fetching, or any Next.js-specific patterns.
Core Principles
- App Router First
-
All routes in src/app/ directory
-
Use page.tsx for routes, layout.tsx for shared layouts
-
Server Components by default, Client Components when needed
-
Route groups with (group) for organization
- Server vs Client Components
Server Components (Default):
-
No "use client" directive needed
-
Can use async/await directly
-
Access database/backend directly
-
Better performance (less JS sent to client)
-
Cannot use hooks or browser APIs
Client Components ("use client"):
-
Use when you need:
-
State (useState, useReducer)
-
Effects (useEffect)
-
Event handlers (onClick, onChange)
-
Browser APIs (localStorage, window)
-
Third-party libraries that use hooks
- Data Fetching Patterns
Server Components:
// Direct async fetch in component export default async function Page() { const data = await fetch('https://api.example.com/data') const json = await data.json() return <div>{json.title}</div> }
Client Components:
'use client' import { useEffect, useState } from 'react'
export default function Page() { const [data, setData] = useState(null)
useEffect(() => { fetch('/api/data') .then(res => res.json()) .then(setData) }, [])
return <div>{data?.title}</div> }
- Server Actions
Use Server Actions for form submissions and mutations:
// app/actions.ts 'use server'
export async function createItem(formData: FormData) { const title = formData.get('title') // Database operation await db.insert({ title }) revalidatePath('/items') redirect('/items') }
// app/form.tsx 'use client' import { createItem } from './actions'
export function Form() { return ( <form action={createItem}> <input name="title" /> <button type="submit">Create</button> </form> ) }
Common Patterns
Route Structure
src/app/ ├── (auth)/ │ ├── login/ │ │ └── page.tsx │ └── signup/ │ └── page.tsx ├── (dashboard)/ │ ├── layout.tsx # Shared dashboard layout │ ├── page.tsx # Dashboard home │ └── settings/ │ └── page.tsx ├── api/ │ └── endpoint/ │ └── route.ts # API routes ├── layout.tsx # Root layout └── page.tsx # Home page
Layouts
// app/(dashboard)/layout.tsx import { Sidebar } from '@/components/sidebar'
export default function DashboardLayout({ children, }: { children: React.ReactNode }) { return ( <div className="flex"> <Sidebar /> <main className="flex-1">{children}</main> </div> ) }
Loading States
// app/dashboard/loading.tsx export default function Loading() { return <div>Loading...</div> }
Error Handling
// app/dashboard/error.tsx 'use client'
export default function Error({ error, reset, }: { error: Error reset: () => void }) { return ( <div> <h2>Something went wrong!</h2> <button onClick={reset}>Try again</button> </div> ) }
Metadata
// app/page.tsx import type { Metadata } from 'next'
export const metadata: Metadata = { title: 'Page Title', description: 'Page description', }
export default function Page() { return <div>Content</div> }
API Routes
// app/api/items/route.ts import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) { const items = await db.getItems() return NextResponse.json({ items }) }
export async function POST(request: NextRequest) { const body = await request.json() const item = await db.createItem(body) return NextResponse.json({ item }, { status: 201 }) }
Dynamic Routes
// app/posts/[id]/page.tsx export default function Post({ params }: { params: { id: string } }) { return <div>Post {params.id}</div> }
// Generate static params export async function generateStaticParams() { const posts = await getPosts() return posts.map((post) => ({ id: post.id })) }
Middleware
// middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) { // Check auth, redirect, rewrite, etc. return NextResponse.next() }
export const config = { matcher: '/dashboard/:path*', }
Environment Variables
// Access in Server Components or Server Actions const apiKey = process.env.API_KEY
// Access in Client Components (must be prefixed with NEXT_PUBLIC_) const publicKey = process.env.NEXT_PUBLIC_API_KEY
Best Practices Checklist
-
Use Server Components by default
-
Add "use client" only when necessary
-
Use Server Actions for mutations
-
Implement loading.tsx for loading states
-
Implement error.tsx for error boundaries
-
Use route groups for organization
-
Add metadata to all pages
-
Use TypeScript for type safety
-
Implement proper error handling
-
Use middleware for auth checks
-
Optimize images with next/image
-
Use dynamic imports for large components
Debugging Tips
-
Hydration Errors: Check for server/client mismatches
-
"use client" Errors: Missing directive on component using hooks
-
Cannot Access Browser APIs: Move to client component
-
Data Not Updating: Use revalidatePath() or revalidateTag()
-
Build Errors: Check for async components without proper typing
Performance Optimization
-
Use React Suspense for loading states
-
Implement streaming with loading.tsx
-
Use dynamic imports for code splitting
-
Optimize images with next/image
-
Use Font optimization with next/font
-
Implement ISR (Incremental Static Regeneration)
-
Use caching with fetch options
When to Use This Skill
Invoke this skill when:
-
Creating new routes or pages
-
Setting up layouts
-
Implementing forms with Server Actions
-
Debugging Next.js-specific errors
-
Optimizing performance
-
Setting up middleware
-
Creating API routes
-
Working with metadata