| name | nextjs-patterns |
| description | Next.js App Router patterns: Server Components, data fetching, routing, middleware, caching, and deployment. |
| metadata | {"thinkfleetbot":{"emoji":"▲","requires":{"anyBins":["npx","node"]}}} |
Next.js Patterns
Modern Next.js with App Router, Server Components, and production patterns.
App Router Structure
app/
layout.tsx # Root layout (wraps all pages)
page.tsx # Home page (/)
loading.tsx # Loading UI (automatic Suspense)
error.tsx # Error boundary
not-found.tsx # 404 page
api/
route.ts # API route (/api)
dashboard/
layout.tsx # Nested layout
page.tsx # Dashboard page (/dashboard)
[id]/
page.tsx # Dynamic route (/dashboard/123)
Server Components (default)
async function UsersPage() {
const users = await db.users.findMany();
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
Client Components
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
Data Fetching
Server Component (preferred)
async function ProductPage({ params }: { params: { id: string } }) {
const product = await fetch(`https://api.example.com/products/${params.id}`, {
next: { revalidate: 3600 },
}).then(r => r.json());
return <Product data={product} />;
}
Server Actions (mutations)
'use server';
export async function createUser(formData: FormData) {
const name = formData.get('name') as string;
await db.users.create({ data: { name } });
revalidatePath('/users');
}
<form action={createUser}>
<input name="name" />
<button type="submit">Create</button>
</form>
API Routes
import { NextResponse } from 'next/server';
export async function GET() {
const users = await db.users.findMany();
return NextResponse.json(users);
}
export async function POST(request: Request) {
const body = await request.json();
const user = await db.users.create({ data: body });
return NextResponse.json(user, { status: 201 });
}
Middleware
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = { matcher: ['/dashboard/:path*'] };
Commands
npx next dev
npx next build
npx next start
npx next lint
ANALYZE=true npx next build
Notes
- Default to Server Components. Only add
'use client' when you need useState, useEffect, or browser APIs.
- Server Components can
await directly — no loading states needed (use loading.tsx for automatic Suspense).
fetch in Server Components is automatically deduped and cached.
- Use
revalidatePath() or revalidateTag() to invalidate cache after mutations.
- Keep Client Components small and push them to leaf nodes of the component tree.