plugins/developer-kit-typescript/skills/nextjs-app-router/SKILL.md
Provides patterns and code examples for building Next.js 16+ applications with App Router architecture. Use when creating projects with App Router, implementing Server Components and Client Components ("use client"), creating Server Actions for forms, building Route Handlers (route.ts), configuring caching with "use cache" directive (cacheLife, cacheTag), setting up parallel routes (`@slot`) or intercepting routes, migrating to proxy.ts, or working with App Router file conventions (layout.tsx, page.tsx, loading.tsx, error.tsx).
npx skillsauth add giuseppe-trisciuoglio/developer-kit nextjs-app-routerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build modern React applications using Next.js 16+ with App Router architecture.
This skill provides patterns for Server Components (default) and Client Components ("use client"), Server Actions for mutations and form handling, Route Handlers for API endpoints, explicit caching with "use cache" directive, parallel and intercepting routes, and Next.js 16 async APIs and proxy.ts.
Activate when user requests involve:
@slot", "intercepting routes"| File | Purpose | Directive | Purpose |
|------|---------|-----------|---------|
| page.tsx | Route page | "use server" | Server Action function |
| layout.tsx | Shared layout | "use client" | Client Component boundary |
| loading.tsx | Suspense loading | "use cache" | Explicit caching (Next.js 16) |
| error.tsx | Error boundary | | |
| not-found.tsx | 404 page | | |
| route.ts | API Route Handler | | |
| proxy.ts | Routing boundary | | |
npx create-next-app@latest my-app --typescript --tailwind --app --turbopack
Server Components are the default in App Router. They run on the server and can use async/await.
// app/users/page.tsx
async function getUsers() {
const apiUrl = process.env.API_URL;
const res = await fetch(`${apiUrl}/users`);
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return <main>{users.map(user => <UserCard key={user.id} user={user} />)}</main>;
}
Add "use client" when using hooks, browser APIs, or event handlers.
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>;
}
Define actions in separate files with "use server" directive.
// app/actions.ts
"use server";
import { revalidatePath } from "next/cache";
export async function createUser(formData: FormData) {
const name = formData.get("name") as string;
const email = formData.get("email") as string;
await db.user.create({ data: { name, email } });
revalidatePath("/users");
}
Use with forms in Client Components:
"use client";
import { useActionState } from "react";
import { createUser } from "./actions";
export default function UserForm() {
const [state, formAction, pending] = useActionState(createUser, {});
return (
<form action={formAction}>
<input name="name" />
<input name="email" type="email" />
<button type="submit" disabled={pending}>{pending ? "Creating..." : "Create"}</button>
</form>
);
}
See references/server-actions.md for Zod validation, optimistic updates, and advanced patterns.
Use "use cache" directive for explicit caching (Next.js 16+).
"use cache";
import { cacheLife, cacheTag } from "next/cache";
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
cacheTag(`product-${id}`);
cacheLife("hours");
const product = await fetchProduct(id);
return <ProductDetail product={product} />;
}
See references/caching-strategies.md for cache profiles, on-demand revalidation, and advanced patterns.
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
return NextResponse.json(await db.user.findMany());
}
export async function POST(request: NextRequest) {
const body = await request.json();
return NextResponse.json(await db.user.create({ data: body }), { status: 201 });
}
Dynamic segments use [param]:
// app/api/users/[id]/route.ts
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const user = await db.user.findUnique({ where: { id } });
if (!user) return NextResponse.json({ error: "Not found" }, { status: 404 });
return NextResponse.json(user);
}
All Next.js APIs are async in version 16.
import { cookies, headers } from "next/headers";
export default async function Page() {
const cookieStore = await cookies();
const headersList = await headers();
const session = cookieStore.get("session")?.value;
const userAgent = headersList.get("user-agent");
return <div>...</div>;
}
Params and searchParams are also Promise-based:
export default async function Page({
params,
searchParams,
}: {
params: Promise<{ slug: string }>;
searchParams: Promise<{ sort?: string }>;
}) {
const { slug } = await params;
const { sort } = await searchParams;
// ...
}
See references/nextjs16-migration.md for migration guide and proxy.ts configuration.
Use @folder convention for parallel route slots.
// app/dashboard/layout.tsx
export default function DashboardLayout({ children, team, analytics }: Record<string, React.ReactNode>) {
return (
<div>
{children}
<div className="grid grid-cols-2">{team}{analytics}</div>
</div>
);
}
// app/dashboard/@team/page.tsx
export default function TeamPage() { return <div>Team Section</div>; }
// app/dashboard/@analytics/page.tsx
export default function AnalyticsPage() { return <div>Analytics Section</div>; }
See references/routing-patterns.md for intercepting routes, route groups, and dynamic routes.
Server vs Client Decision:
Data Fetching:
cache() for deduplicationloading.tsxPerformance Checklist:
loading.tsx for Suspense boundariesnext/image for optimized imagesnext/font for font optimizationerror.tsx and not-found.tsx for error handlingInput: Create a form to submit blog posts with Zod validation
Output:
// app/blog/actions.ts
"use server";
import { z } from "zod";
import { revalidatePath } from "next/cache";
const schema = z.object({ title: z.string().min(5), content: z.string().min(10) });
export async function createPost(formData: FormData) {
const parsed = schema.safeParse({ title: formData.get("title"), content: formData.get("content") });
if (!parsed.success) return { errors: parsed.error.flatten().fieldErrors };
await db.post.create({ data: parsed.data });
revalidatePath("/blog");
return { success: true };
}
// app/blog/new/page.tsx
"use client";
import { useActionState } from "react";
import { createPost } from "../actions";
export default function NewPostPage() {
const [state, formAction, pending] = useActionState(createPost, {});
return (
<form action={formAction}>
<input name="title" placeholder="Title" />
{state.errors?.title && <span>{state.errors.title[0]}</span>}
<textarea name="content" placeholder="Content" />
{state.errors?.content && <span>{state.errors.content[0]}</span>}
<button type="submit" disabled={pending}>{pending ? "Publishing..." : "Publish"}</button>
</form>
);
}
Input: Create a cached product page with on-demand revalidation
Output:
// app/products/[id]/page.tsx
"use cache";
import { cacheLife, cacheTag } from "next/cache";
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
cacheTag(`product-${id}`, "products");
cacheLife("hours");
const product = await db.product.findUnique({ where: { id } });
if (!product) notFound();
return <article><h1>{product.name}</h1><p>{product.description}</p></article>;
}
// app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const { tag } = await request.json();
revalidateTag(tag);
return NextResponse.json({ revalidated: true });
}
Input: Create a dashboard with sidebar and stats areas
Output:
// app/dashboard/layout.tsx
export default function DashboardLayout({ children, sidebar, stats }: Record<string, React.ReactNode>) {
return (
<div className="flex">
<aside className="w-64">{sidebar}</aside>
<main className="flex-1"><div className="grid grid-cols-3">{stats}</div>{children}</main>
</div>
);
}
// app/dashboard/@sidebar/page.tsx
export default function Sidebar() { return <nav>{/* Navigation links */}</nav>; }
// app/dashboard/@stats/page.tsx
export default async function Stats() {
const stats = await fetchStats();
return <><div>Users: {stats.users}</div><div>Orders: {stats.orders}</div></>;
}
Constraints:
cookies(), headers(), draftMode() are async in Next.js 16params and searchParams are Promise-based in Next.js 16"use server" directiveWarnings:
await in a Client Component causes a build errorwindow or document in Server Components throws an errorawait cookies() or headers() in Next.js 16 returns a Promise instead of the valuefetch() third-party URLs process untrusted content — always validate, sanitize, and type-check responses; use environment variables for API URLs rather than hardcoding themdevelopment
Explore codebase before committing to a change. Phase executor skill for specs.explore command.
development
Executes real end-to-end verification against a running application after specification implementation. Detects the application type, starts the local runtime (Docker, Node, Spring Boot, etc.), runs real tests (curl for REST APIs, Playwright for web SPAs, computer-use for desktop apps), verifies acceptance criteria from the functional specification, generates a markdown report, and tears down the environment. Use when: user asks to verify a completed spec with real tests, run e2e checks after implementation, validate acceptance criteria in a live environment, or test the feature for real after task completion.
development
Initialize Spec-Driven Development context — detects tech stack, conventions, architecture patterns, and bootstraps persistence backends. Triggers on 'sdd-init', 'init sdd', 'setup sdd', 'initialize sdd', 'setup project', 'initialize project context'. Creates/updates docs/specs/architecture.md & ontology.md (Constitution), and populates knowledge-graph.json.
development
Optimizes raw idea descriptions into structured prompts ready for the brainstorming workflow. TRIGGER when: user says "optimize for brainstorm", "prepare idea for brainstorm", "enhance this idea", "make this ready for brainstorming", "imposta per brainstorm", or wants to improve a feature idea before using /specs.brainstorm. DO NOT TRIGGER for code optimization, refactoring, or general prompt engineering tasks.