Next.js
The Next.js skill provides expertise in Next.js with App Router, Server Components, Server Actions, and full-stack development patterns.
When Activated
Section titled “When Activated”- React applications with SSR/SSG
- Full-stack applications
- App Router patterns
- Working in
app/directory
Core Patterns
Section titled “Core Patterns”App Router Structure
Section titled “App Router Structure”app/├── layout.tsx # Root layout├── page.tsx # Home page├── loading.tsx # Loading UI├── error.tsx # Error UI├── api/│ └── users/│ └── route.ts # API route└── users/ ├── page.tsx # Users page └── [id]/ └── page.tsx # User detailServer Components
Section titled “Server Components”// app/users/page.tsx - Server Component (default)async function UsersPage() { const users = await db.users.findMany();
return ( <ul> {users.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> );}Client Components
Section titled “Client Components”'use client';
import { useState } from 'react';
export function Counter() { const [count, setCount] = useState(0);
return ( <button onClick={() => setCount(c => c + 1)}> Count: {count} </button> );}API Routes
Section titled “API Routes”import { NextRequest, NextResponse } from 'next/server';
export async function GET() { const users = await db.users.findMany(); return NextResponse.json(users);}
export async function POST(request: NextRequest) { const data = await request.json(); const user = await db.users.create({ data }); return NextResponse.json(user, { status: 201 });}Server Actions
Section titled “Server Actions”'use server';
export async function createUser(formData: FormData) { const name = formData.get('name') as string; await db.users.create({ data: { name } }); revalidatePath('/users');}
// app/users/new/page.tsximport { createUser } from '@/app/actions';
export default function NewUserPage() { return ( <form action={createUser}> <input name="name" required /> <button type="submit">Create</button> </form> );}Dynamic Routes
Section titled “Dynamic Routes”interface PageProps { params: { id: string };}
export default async function UserPage({ params }: PageProps) { const user = await db.users.findUnique({ where: { id: params.id } });
if (!user) { notFound(); }
return <div>{user.name}</div>;}Layouts
Section titled “Layouts”export default function RootLayout({ children,}: { children: React.ReactNode;}) { return ( <html lang="en"> <body> <nav>{/* Navigation */}</nav> <main>{children}</main> <footer>{/* Footer */}</footer> </body> </html> );}Best Practices
Section titled “Best Practices”- Use Server Components by default
- Add ‘use client’ only when needed
- Colocate data fetching with components
- Use loading.tsx for suspense boundaries
- Implement proper error boundaries
Common Pitfalls
Section titled “Common Pitfalls”Using Hooks in Server Components
Section titled “Using Hooks in Server Components”// ❌ BAD: Hooks in Server Componentexport default async function Page() { const [state, setState] = useState(0); // Error! return <div>{state}</div>;}
// ✅ GOOD: Mark as Client Component'use client';
export default function Page() { const [state, setState] = useState(0); return <div>{state}</div>;}Large Client Bundles
Section titled “Large Client Bundles”// ❌ BAD: Entire page as Client Component'use client';
export default function Page() { const [count, setCount] = useState(0); const data = await fetchData(); // Can't use await in Client Component
return ( <div> <HeavyComponent /> <button onClick={() => setCount(c => c + 1)}>{count}</button> </div> );}
// ✅ GOOD: Extract interactive parts onlyexport default async function Page() { const data = await fetchData(); // Server Component can await
return ( <div> <HeavyComponent data={data} /> {/* Server Component */} <Counter /> {/* Small Client Component */} </div> );}
// Counter.tsx'use client';export function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(c => c + 1)}>{count}</button>;}Missing Loading States
Section titled “Missing Loading States”// ❌ BAD: No loading stateexport default async function Page() { const data = await slowFetch(); return <div>{data}</div>;}
// ✅ GOOD: Add loading.tsx// app/page.tsxexport default async function Page() { const data = await slowFetch(); return <div>{data}</div>;}
// app/loading.tsxexport default function Loading() { return <div>Loading...</div>;}Not Revalidating After Mutations
Section titled “Not Revalidating After Mutations”// ❌ BAD: No revalidation'use server';export async function createUser(data: FormData) { await db.users.create({ data: getData(data) }); // Page still shows old data}
// ✅ GOOD: Revalidate path'use server';export async function createUser(data: FormData) { await db.users.create({ data: getData(data) }); revalidatePath('/users'); redirect('/users');}Data Fetching Patterns
Section titled “Data Fetching Patterns”Parallel Data Fetching
Section titled “Parallel Data Fetching”export default async function Page() { // Fetch in parallel const [users, posts] = await Promise.all([ fetchUsers(), fetchPosts(), ]);
return ( <div> <UserList users={users} /> <PostList posts={posts} /> </div> );}Sequential Data Fetching
Section titled “Sequential Data Fetching”export default async function UserPage({ params }: { params: { id: string } }) { const user = await fetchUser(params.id); const posts = await fetchUserPosts(user.id); // Depends on user
return ( <div> <h1>{user.name}</h1> <PostList posts={posts} /> </div> );}Streaming with Suspense
Section titled “Streaming with Suspense”import { Suspense } from 'react';
export default function Page() { return ( <div> <h1>Dashboard</h1> <Suspense fallback={<Skeleton />}> <SlowComponent /> </Suspense> <FastComponent /> </div> );}
async function SlowComponent() { const data = await slowFetch(); return <div>{data}</div>;}Caching and Revalidation
Section titled “Caching and Revalidation”Time-Based Revalidation
Section titled “Time-Based Revalidation”// Revalidate every hourexport const revalidate = 3600;
export default async function Page() { const data = await fetch('https://api.example.com/data', { next: { revalidate: 3600 } }); return <div>{JSON.stringify(data)}</div>;}On-Demand Revalidation
Section titled “On-Demand Revalidation”'use server';
import { revalidatePath, revalidateTag } from 'next/cache';
export async function createPost(data: FormData) { await db.posts.create({ data: getData(data) }); revalidatePath('/posts'); // or revalidateTag('posts');}Metadata
Section titled “Metadata”import { Metadata } from 'next';
export const metadata: Metadata = { title: 'User Profile', description: 'View user profile information',};
export default function Page() { return <div>Profile</div>;}
// Dynamic metadataexport async function generateMetadata({ params }: PageProps): Promise<Metadata> { const user = await fetchUser(params.id);
return { title: `${user.name}'s Profile`, description: `Profile page for ${user.name}`, };}Integration with Other Skills
Section titled “Integration with Other Skills”With React
Section titled “With React”See React skill for component patterns and hooks.
With TypeScript
Section titled “With TypeScript”Full TypeScript integration:
import { NextRequest, NextResponse } from 'next/server';
export async function GET( request: NextRequest, { params }: { params: { id: string } }): Promise<NextResponse> { const user = await fetchUser(params.id); return NextResponse.json(user);}Related Skills
Section titled “Related Skills”- React - Component patterns
- TypeScript - Type safety
- Tailwind CSS - Styling
- PostgreSQL - Database