Provides code patterns for Next.js 16+ App Router including Server Components, Client Components, Server Actions, Route Handlers, caching with 'use cache', parallel routes, and files like layout.tsx, page.tsx.
From developer-kit-typescriptnpx claudepluginhub giuseppe-trisciuoglio/developer-kit --plugin developer-kit-typescriptThis skill is limited to using the following tools:
references/app-router-fundamentals.mdreferences/best-practices.mdreferences/caching-strategies.mdreferences/data-fetching.mdreferences/examples.mdreferences/metadata-api.mdreferences/nextjs16-migration.mdreferences/patterns.mdreferences/routing-patterns.mdreferences/server-actions.mdSearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides agent creation for Claude Code plugins with file templates, frontmatter specs (name, description, model), triggering examples, system prompts, and best practices.
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 them