Agent skill
nextjs-app-router
Next.js 15 App Router patterns for Server/Client Components, async params, layouts, route handlers, Server Actions, and data fetching. Use when creating routes, pages, layouts, API endpoints, or implementing form submissions with revalidation.
Install this agent skill to your Project
npx add-skill https://github.com/canatufkansu/claude-skills/tree/main/nextjs-app-router
SKILL.md
Next.js App Router
Server vs Client Components
Default to Server Components. Only add 'use client' when you need:
- Event handlers (onClick, onChange, onSubmit)
- Browser APIs (localStorage, window, navigator)
- React hooks (useState, useEffect, useRef)
- Third-party client libraries
// Server Component (default) - no directive needed
export default async function Page() {
const data = await fetchData(); // Direct async/await
return <div>{data.title}</div>;
}
// Client Component - explicit directive
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
Next.js 15 Async Params (Critical)
Params and searchParams are now Promises and must be awaited:
// ✅ Correct - Next.js 15
type Props = {
params: Promise<{ locale: string; slug: string }>;
searchParams: Promise<{ [key: string]: string | undefined }>;
};
export default async function Page({ params, searchParams }: Props) {
const { locale, slug } = await params;
const { theme } = await searchParams;
return <div>Locale: {locale}, Slug: {slug}</div>;
}
// ✅ generateMetadata also uses async params
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
return { title: `Page - ${locale}` };
}
Route File Conventions
app/
├── layout.tsx # Root layout (required)
├── page.tsx # Home page (/)
├── loading.tsx # Loading UI (Suspense boundary)
├── error.tsx # Error boundary ('use client' required)
├── not-found.tsx # 404 page
├── [locale]/
│ ├── layout.tsx # Nested layout
│ ├── page.tsx # /[locale]
│ └── services/
│ ├── page.tsx # /[locale]/services
│ └── [slug]/
│ └── page.tsx # /[locale]/services/[slug]
└── api/
└── route.ts # API route handler
Layouts and Templates
// app/[locale]/layout.tsx
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
return (
<html lang={locale}>
<body>{children}</body>
</html>
);
}
Server Actions
// lib/actions.ts
'use server';
import { revalidatePath, revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';
export async function submitForm(formData: FormData) {
const email = formData.get('email') as string;
// Validate with Zod (see zod-react-hook-form skill)
// Process data...
revalidatePath('/[locale]/contact'); // Revalidate specific path
// OR revalidateTag('contact-submissions'); // Revalidate by tag
redirect('/success'); // Optional redirect
}
// Usage in Client Component
'use client';
export function ContactForm() {
return (
<form action={submitForm}>
<input name="email" type="email" required />
<button type="submit">Submit</button>
</form>
);
}
Route Handlers (API Routes)
// app/api/webhook/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.json();
// Process webhook...
return NextResponse.json({ success: true }, { status: 200 });
}
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const id = searchParams.get('id');
return NextResponse.json({ id });
}
Data Fetching Patterns
// Server Component with fetch
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }, // ISR: revalidate every hour
// OR cache: 'no-store', // SSR: always fresh
// OR next: { tags: ['data'] }, // On-demand with revalidateTag
});
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
}
export default async function Page() {
const data = await getData();
return <div>{data.title}</div>;
}
Static Generation
// Generate static params for dynamic routes
export async function generateStaticParams() {
const locales = ['pt-PT', 'en', 'tr', 'es', 'fr', 'de'];
const services = await getServices();
return locales.flatMap(locale =>
services.map(service => ({
locale,
slug: service.slug,
}))
);
}
Anti-Patterns to Avoid
// ❌ Don't use params directly without awaiting (Next.js 15)
export default function Page({ params }: { params: { id: string } }) {
return <div>{params.id}</div>; // Will cause errors
}
// ❌ Don't fetch in Client Components when Server Components work
'use client';
export default function Page() {
const [data, setData] = useState(null);
useEffect(() => { fetch('/api/data')... }, []); // Unnecessary
}
// ❌ Don't use 'use client' on entire pages unless necessary
'use client';
export default function Page() {
return <div>Static content</div>; // Should be Server Component
}
// ❌ Don't import Server Components into Client Components
// Server Components can only be passed as children/props
References
For detailed patterns, see:
- PATTERNS.md - Advanced composition patterns
- DATA-FETCHING.md - Caching strategies
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
framer-motion-animations
Subtle animation patterns for hero sections, card reveals, page transitions, and scroll-triggered effects using Framer Motion. Use when adding animations to components, implementing scroll effects, or creating page transitions.
json-content-management
JSON-driven content architecture for services, programmes, FAQs, testimonials, and policies with TypeScript interfaces and locale-aware data fetching. Use when defining content schemas, creating data utilities, adding new content types, or fetching localized business data.
accessibility-a11y
Semantic HTML, keyboard navigation, focus states, ARIA labels, skip links, and WCAG contrast requirements. Use when ensuring accessibility compliance, implementing keyboard navigation, or adding screen reader support.
email-resend
Email sending via Resend API for contact forms and booking requests with locale-aware templates. Use when implementing email notifications, contact form submissions, or transactional emails.
zod-react-hook-form
Form validation combining Zod schemas with React Hook Form, including localized error messages, Server Action integration, and shadcn/ui Form components. Use when building forms, validating user input, handling form submissions, or implementing Server Actions with validation.
typescript-strict
TypeScript strict mode patterns with interfaces, type guards, generics, and utility types. Use when defining types, creating type-safe functions, handling nullable values, or implementing generic components.
Didn't find tool you were looking for?