Gentleman-Skills nextjs-15
install
source · Clone the upstream repo
git clone https://github.com/Gentleman-Programming/Gentleman-Skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Gentleman-Programming/Gentleman-Skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/curated/nextjs-15" ~/.claude/skills/gentleman-programming-gentleman-skills-nextjs-15 && rm -rf "$T"
manifest:
curated/nextjs-15/SKILL.mdsource content
App Router File Conventions
app/ ├── layout.tsx # Root layout (required) ├── page.tsx # Home page (/) ├── loading.tsx # Loading UI (Suspense) ├── error.tsx # Error boundary ├── not-found.tsx # 404 page ├── (auth)/ # Route group (no URL impact) │ ├── login/page.tsx # /login │ └── signup/page.tsx # /signup ├── api/ │ └── route.ts # API handler └── _components/ # Private folder (not routed)
Server Components (Default)
// No directive needed - async by default export default async function Page() { const data = await db.query(); return <Component data={data} />; }
Server Actions
// app/actions.ts "use server"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; export async function createUser(formData: FormData) { const name = formData.get("name") as string; await db.users.create({ data: { name } }); revalidatePath("/users"); redirect("/users"); } // Usage <form action={createUser}> <input name="name" required /> <button type="submit">Create</button> </form>
Data Fetching
// Parallel async function Page() { const [users, posts] = await Promise.all([ getUsers(), getPosts(), ]); return <Dashboard users={users} posts={posts} />; } // Streaming with Suspense <Suspense fallback={<Loading />}> <SlowComponent /> </Suspense>
Route Handlers (API)
// app/api/users/route.ts import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { const users = await db.users.findMany(); return NextResponse.json(users); } export async function POST(request: NextRequest) { const body = await request.json(); const user = await db.users.create({ data: body }); return NextResponse.json(user, { status: 201 }); }
Middleware
// middleware.ts (root level) import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; export function middleware(request: NextRequest) { const token = request.cookies.get("token"); if (!token && request.nextUrl.pathname.startsWith("/dashboard")) { return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next(); } export const config = { matcher: ["/dashboard/:path*"], };
Metadata
// Static export const metadata = { title: "My App", description: "Description", }; // Dynamic export async function generateMetadata({ params }) { const product = await getProduct(params.id); return { title: product.name }; }
server-only Package
import "server-only"; // This will error if imported in client component export async function getSecretData() { return db.secrets.findMany(); }
Keywords
nextjs, next.js, app router, server components, server actions, streaming