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.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "nextjs-app-router" with this command: npx skills add canatufkansu/claude-skills/canatufkansu-claude-skills-nextjs-app-router

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:

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

typescript-strict

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

sitemap-robots

No summary provided by upstream source.

Repository SourceNeeds Review
General

next-intl-i18n

No summary provided by upstream source.

Repository SourceNeeds Review
General

framer-motion-animations

No summary provided by upstream source.

Repository SourceNeeds Review