nextjs
SKILL.md
Next.js 15 Development Expert
Expert in Next.js 15 with App Router, Server Components, Server Actions, and full-stack development patterns. Specialized in building performant, SEO-friendly applications.
When to Use
- Next.js projects (App Router)
- Server-side rendered (SSR) applications
- Static site generation (SSG)
- Full-stack React applications
- Projects requiring SEO optimization
For pure React + Vite projects, use react agent instead.
Technology Stack
Core
- Next.js 15: App Router, Server Components, Server Actions
- React 19: useActionState, useOptimistic, Suspense
- TypeScript: Strict typing
UI/Styling
- shadcn/ui: Component library
- Radix UI: Accessible primitives
- TailwindCSS: Utility-first styling
- CVA: Component variant management
Data & Forms
- Server Actions: Form handling and mutations
- Zod: Validation (shared client/server)
- TanStack Query: Client-side data fetching (when needed)
- TanStack Table: Data tables
- TanStack Virtual: Virtualized lists
- React Hook Form + Zod: Form handling
- Prisma: Database ORM
- Zustand: Client state management
Charts
- Recharts: Data visualization
Project Structure
/my-nextjs-app
├── /public/ # Static files
├── /src/
│ ├── /app/ # App Router
│ │ ├── /(auth)/ # Route group: auth pages
│ │ │ ├── login/
│ │ │ │ └── page.tsx
│ │ │ ├── register/
│ │ │ │ └── page.tsx
│ │ │ └── layout.tsx
│ │ ├── /(dashboard)/ # Route group: dashboard
│ │ │ ├── dashboard/
│ │ │ │ └── page.tsx
│ │ │ ├── users/
│ │ │ │ ├── page.tsx
│ │ │ │ └── [id]/
│ │ │ │ └── page.tsx
│ │ │ └── layout.tsx
│ │ ├── api/ # API routes (if needed)
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Home page
│ │ ├── loading.tsx # Loading UI
│ │ ├── error.tsx # Error UI
│ │ ├── not-found.tsx # 404 page
│ │ └── globals.css
│ ├── /components/ # Shared components
│ │ ├── /ui/ # shadcn/ui components
│ │ ├── /forms/ # Form components
│ │ └── /data-display/ # Tables, Charts
│ ├── /features/ # Feature modules
│ │ ├── /auth/
│ │ │ ├── /components/
│ │ │ ├── /actions/ # Server Actions
│ │ │ └── /schemas/ # Zod schemas
│ │ └── /users/
│ ├── /hooks/ # Custom hooks
│ ├── /lib/ # Library configs (db, auth)
│ ├── /services/ # External API services
│ ├── /types/ # TypeScript types
│ └── /utils/ # Utilities
├── next.config.ts
├── tailwind.config.ts
├── tsconfig.json
└── package.json
Core Patterns
Server Component (Default)
// app/users/page.tsx
import { getUsers } from "@/features/users/actions";
import { UsersTable } from "@/features/users/components/UsersTable";
export default async function UsersPage() {
const users = await getUsers();
return (
<div className="container py-8">
<h1 className="text-2xl font-bold mb-6">Users</h1>
<UsersTable users={users} />
</div>
);
}
Client Component
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<Button onClick={() => setCount(count + 1)}>Increment</Button>
</div>
);
}
Server Action
// features/users/actions/createUser.ts
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { z } from "zod";
import { db } from "@/lib/db";
const createUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
export type CreateUserState = {
errors?: {
name?: string[];
email?: string[];
};
message?: string;
};
export async function createUser(
prevState: CreateUserState,
formData: FormData
): Promise<CreateUserState> {
// Validate
const validated = createUserSchema.safeParse({
name: formData.get("name"),
email: formData.get("email"),
});
if (!validated.success) {
return {
errors: validated.error.flatten().fieldErrors,
};
}
// Create user
try {
await db.user.create({
data: validated.data,
});
} catch (error) {
return { message: "Failed to create user" };
}
// Revalidate and redirect
revalidatePath("/users");
redirect("/users");
}
Form with useActionState
"use client";
import { useActionState } from "react";
import { createUser, type CreateUserState } from "@/features/users/actions";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
const initialState: CreateUserState = {};
export function CreateUserForm() {
const [state, formAction, isPending] = useActionState(createUser, initialState);
return (
<form action={formAction} className="space-y-4">
<div>
<Label htmlFor="name">Name</Label>
<Input id="name" name="name" required />
{state.errors?.name && (
<p className="text-sm text-destructive">{state.errors.name[0]}</p>
)}
</div>
<div>
<Label htmlFor="email">Email</Label>
<Input id="email" name="email" type="email" required />
{state.errors?.email && (
<p className="text-sm text-destructive">{state.errors.email[0]}</p>
)}
</div>
{state.message && (
<p className="text-sm text-destructive">{state.message}</p>
)}
<Button type="submit" disabled={isPending}>
{isPending ? "Creating..." : "Create User"}
</Button>
</form>
);
}
Optimistic Updates
"use client";
import { useOptimistic } from "react";
import { toggleTodo } from "@/features/todos/actions";
export function TodoItem({ todo }) {
const [optimisticTodo, setOptimisticTodo] = useOptimistic(
todo,
(state, completed: boolean) => ({ ...state, completed })
);
async function handleToggle() {
setOptimisticTodo(!optimisticTodo.completed);
await toggleTodo(todo.id);
}
return (
<div className={optimisticTodo.completed ? "line-through" : ""}>
<button onClick={handleToggle}>{todo.title}</button>
</div>
);
}
Layout with Authentication
// app/(dashboard)/layout.tsx
import { redirect } from "next/navigation";
import { getSession } from "@/lib/auth";
import { Sidebar } from "@/components/Sidebar";
import { Header } from "@/components/Header";
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getSession();
if (!session) {
redirect("/login");
}
return (
<div className="flex h-screen">
<Sidebar />
<div className="flex-1 flex flex-col">
<Header user={session.user} />
<main className="flex-1 overflow-auto p-6">
{children}
</main>
</div>
</div>
);
}
Loading and Error States
// app/(dashboard)/users/loading.tsx
import { Skeleton } from "@/components/ui/skeleton";
export default function UsersLoading() {
return (
<div className="space-y-4">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-64 w-full" />
</div>
);
}
// app/(dashboard)/users/error.tsx
"use client";
import { Button } from "@/components/ui/button";
export default function UsersError({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="flex flex-col items-center justify-center gap-4">
<h2>Something went wrong!</h2>
<p className="text-muted-foreground">{error.message}</p>
<Button onClick={reset}>Try again</Button>
</div>
);
}
Parallel Routes (Dashboard)
// app/(dashboard)/@analytics/page.tsx
export default function AnalyticsSlot() {
return <AnalyticsChart />;
}
// app/(dashboard)/@users/page.tsx
export default function UsersSlot() {
return <RecentUsers />;
}
// app/(dashboard)/layout.tsx
export default function DashboardLayout({
children,
analytics,
users,
}: {
children: React.ReactNode;
analytics: React.ReactNode;
users: React.ReactNode;
}) {
return (
<div className="grid grid-cols-2 gap-4">
<div>{analytics}</div>
<div>{users}</div>
<div className="col-span-2">{children}</div>
</div>
);
}
Data Fetching Patterns
Server-Side Data
// Fetch in Server Component
async function getUsers() {
const res = await fetch("https://api.example.com/users", {
next: { revalidate: 60 }, // Revalidate every 60 seconds
});
return res.json();
}
// Or with cache tags
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`, {
next: { tags: [`user-${id}`] },
});
return res.json();
}
// Revalidate by tag
revalidateTag(`user-${id}`);
Database Queries
// lib/db.ts
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const db = globalForPrisma.prisma || new PrismaClient();
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = db;
// Usage in Server Component or Action
const users = await db.user.findMany();
Next.js Configuration
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
typedRoutes: true,
},
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**.example.com",
},
],
},
};
export default nextConfig;
Best Practices
-
Server vs Client Components
- Default to Server Components
- Use Client Components only for interactivity
- Keep client bundles small
-
Data Fetching
- Fetch in Server Components when possible
- Use Server Actions for mutations
- Implement proper caching strategies
-
Forms
- Use Server Actions for form handling
- Validate with Zod on both client and server
- Implement optimistic updates for UX
-
Performance
- Use
loading.tsxfor Suspense boundaries - Implement streaming with
Suspense - Use
generateStaticParamsfor static pages
- Use
-
Security
- Always validate server-side
- Use CSRF protection (automatic with Server Actions)
- Sanitize user inputs
-
SEO
- Use
generateMetadatafor dynamic meta tags - Implement proper Open Graph tags
- Use semantic HTML
- Use
Quick Setup Commands
# Create new Next.js project
npx create-next-app@latest my-app --typescript --tailwind --eslint --app --src-dir
cd my-app
# Add shadcn/ui
npx shadcn@latest init
# Add common components
npx shadcn@latest add button input label form card table
# Add database (Prisma)
npm install prisma @prisma/client
npx prisma init
# Add validation
npm install zod
# Add TanStack Table (for complex tables)
npm install @tanstack/react-table
Middleware Pattern
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("token");
// Protect dashboard routes
if (request.nextUrl.pathname.startsWith("/dashboard") && !token) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"],
};
Weekly Installs
4
Repository
0xkynz/codekitGitHub Stars
1
First Seen
10 days ago
Security Audits
Installed on
opencode4
antigravity4
claude-code4
github-copilot4
codex4
zencoder4