Loading...
Loading...
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 skill4agent add giuseppe-trisciuoglio/developer-kit nextjs-app-router| File | Purpose |
|---|---|
| Route page component |
| Shared layout wrapper |
| Suspense loading UI |
| Error boundary |
| 404 page |
| Re-mounting layout |
| API Route Handler |
| Parallel route fallback |
| Routing boundary (Next.js 16) |
| Directive | Purpose |
|---|---|
| Mark Server Action functions |
| Mark Client Component boundary |
| Enable explicit caching (Next.js 16) |
npx create-next-app@latest my-app --typescript --tailwind --app --turbopack// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://api.example.com/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>
);
}"use client""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>
);
}// 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 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>
);
}"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} />;
}// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const users = await db.user.findMany();
return NextResponse.json(users);
}
export async function POST(request: NextRequest) {
const body = await request.json();
const user = await db.user.create({ data: body });
return NextResponse.json(user, { status: 201 });
}[param]// app/api/users/[id]/route.ts
interface RouteParams {
params: Promise<{ id: string }>;
}
export async function GET(request: NextRequest, { params }: RouteParams) {
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);
}import { cookies, headers, draftMode } from "next/headers";
export default async function Page() {
const cookieStore = await cookies();
const headersList = await headers();
const { isEnabled } = await draftMode();
const session = cookieStore.get("session")?.value;
const userAgent = headersList.get("user-agent");
return <div>...</div>;
}export default async function Page({
params,
searchParams,
}: {
params: Promise<{ slug: string }>;
searchParams: Promise<{ sort?: string }>;
}) {
const { slug } = await params;
const { sort } = await searchParams;
// ...
}@folder// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
team,
analytics,
}: {
children: React.ReactNode;
team: React.ReactNode;
analytics: 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>;
}cache()loading.tsx"use cache";
import { cacheLife, cacheTag } from "next/cache";
// Set cache duration
cacheLife("hours");
// Tag for revalidation
cacheTag("resource-name");loading.tsxnext/imagenext/fontnext.config.tserror.tsxnot-found.tsx// 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>
);
}// 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>
<p>${product.price}</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 });
}// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
sidebar,
stats,
}: {
children: React.ReactNode;
sidebar: React.ReactNode;
stats: 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>
<div>Revenue: {stats.revenue}</div>
</>
);
}cookies()headers()draftMode()paramssearchParamsawaitwindowdocumentcookies()headers()