.claude/skills/nextjs-app-router/SKILL.md
Next.js 16 App Router patterns including server components, client components, server actions, route handlers, layouts, metadata API, dynamic routes, file conventions, data fetching, caching strategies, and Next.js best practices for building modern React applications
npx skillsauth add DW225/ree-board 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.
Activate this skill when working on:
Use Server Components (default) when:
Use Client Components ('use client') when:
useState, useEffect, useContext)Example:
// app/board/[id]/page.tsx - Server Component (default)
import { getBoard } from "@/lib/actions/board/getBoard";
export default async function BoardPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const board = await getBoard(id);
return <BoardView board={board} />;
}
// components/board/BoardView.tsx - Client Component
"use client";
import { useState } from "react";
import { PostProvider } from "@/components/board/PostProvider";
export function BoardView({ board }) {
const [filter, setFilter] = useState("all");
return <PostProvider boardId={board.id}>{/* Interactive UI */}</PostProvider>;
}
CRITICAL: All server actions MUST use actionWithAuth or rbacWithAuth wrappers (see rbac-security skill).
Pattern:
// lib/actions/post/createPost.ts
"use server";
import { rbacWithAuth } from "@/lib/actions/actionWithAuth";
import { db } from "@/db";
import { postTable } from "@/db/schema";
export const createPost = async (
boardId: string,
content: string,
type: PostType
) =>
rbacWithAuth(boardId, async (userId) => {
const post = await db
.insert(postTable)
.values({
id: nanoid(),
boardId,
userId,
content,
type,
createdAt: new Date(),
})
.returning();
return post[0];
});
Usage in Client Components:
"use client";
import { createPost } from "@/lib/actions/post/createPost";
import { useTransition } from "react";
export function CreatePostForm({ boardId }) {
const [isPending, startTransition] = useTransition();
const handleSubmit = async (formData: FormData) => {
startTransition(async () => {
await createPost(boardId, formData.get("content") as string, "went_well");
});
};
return <form action={handleSubmit}>...</form>;
}
Special Files:
page.tsx - Unique UI for a routelayout.tsx - Shared UI for segments and childrenloading.tsx - Loading UI (Suspense boundary)error.tsx - Error UI (Error boundary)not-found.tsx - Not found UIroute.ts - API endpoint (Route Handler)Route Organization:
app/
├── layout.tsx # Root layout
├── page.tsx # Home page
├── board/
│ ├── layout.tsx # Board layout
│ ├── page.tsx # Board list
│ └── [id]/
│ ├── page.tsx # Board detail (dynamic route)
│ ├── loading.tsx # Loading state
│ └── error.tsx # Error handling
└── api/
└── webhooks/
└── route.ts # API route handler
Static Metadata:
// app/board/[id]/page.tsx
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Board Details",
description: "View and manage your retrospective board",
};
Dynamic Metadata:
// app/board/[id]/page.tsx
import { getBoard } from "@/lib/actions/board/getBoard";
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>;
}): Promise<Metadata> {
const { id } = await params;
const board = await getBoard(id);
return {
title: `${board.name} - Ree Board`,
description: board.description,
openGraph: {
title: board.name,
description: board.description,
},
};
}
Lazy Load Heavy Components:
import dynamic from "next/dynamic";
// Drag-and-drop is lazy loaded in the project
const PostDragDrop = dynamic(() => import("@/components/board/PostDragDrop"), {
ssr: false,
loading: () => <LoadingSkeleton />,
});
Bad:
"use client"; // ❌ Unnecessary - no hooks or interactivity
export default function Page() {
return <div>Static content</div>;
}
Good:
// ✅ Server component by default
export default function Page() {
return <div>Static content</div>;
}
Bad:
"use client";
export default function Page() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("/api/data")
.then((r) => r.json())
.then(setData); // ❌
}, []);
}
Good:
// ✅ Server component fetches data
export default async function Page() {
const data = await getData();
return <ClientView data={data} />;
}
Bad:
"use server";
export async function deleteBoard(id: string) {
// ❌ No auth check - security vulnerability
await db.delete(boardTable).where(eq(boardTable.id, id));
}
Good:
"use server";
export const deleteBoard = async (id: string) =>
rbacWithAuth(id, async (userId) => {
// ✅ Authentication and RBAC enforced
await db.delete(boardTable).where(eq(boardTable.id, id));
});
Bad:
export default async function Page() {
const data = await slowDataFetch(); // ❌ Blocks entire page
return <div>{data}</div>;
}
Good:
import { Suspense } from "react";
export default function Page() {
return (
<Suspense fallback={<Loading />}>
<DataComponent />
</Suspense>
);
}
async function DataComponent() {
const data = await slowDataFetch();
return <div>{data}</div>;
}
app/ - Next.js App Router directoryapp/layout.tsx - Root layout with providersapp/board/[id]/page.tsx - Dynamic board pagelib/actions/ - Server actions by domainproxy.ts - Supabase authentication proxy (Next.js 16)next.config.js - Next.js configurationlib/actions/[entity]/actionWithAuth or rbacWithAuthLast Updated: 2026-01-10
development
Jest testing strategies, test organization, factory patterns for test data, mocking strategies for authentication and external services, real-time message processor testing, test-driven development workflow, unit vs integration testing, fake timer usage for time-dependent tests, and testing best practices for ree-board project
development
Preact Signals for reactive state management, signal vs computed signal usage, batch updates for performance, action creator patterns, signal integration with React components, state management by domain (boards posts members), reactive patterns, and signal best practices for ree-board project
testing
Role-based access control (RBAC) patterns, authentication wrappers, authorization checks, input validation with Zod schemas, security boundaries, server action security, real-time message validation, preventing common vulnerabilities like XSS and SQL injection, and security best practices for ree-board project
data-ai
Drizzle ORM best practices including schema design with relationships, database migrations, prepared statements for performance, transactions, indexes, Turso SQLite database operations, type safety patterns, query optimization, and database workflow for ree-board project