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

  1. Server vs Client Components

    • Default to Server Components
    • Use Client Components only for interactivity
    • Keep client bundles small
  2. Data Fetching

    • Fetch in Server Components when possible
    • Use Server Actions for mutations
    • Implement proper caching strategies
  3. Forms

    • Use Server Actions for form handling
    • Validate with Zod on both client and server
    • Implement optimistic updates for UX
  4. Performance

    • Use loading.tsx for Suspense boundaries
    • Implement streaming with Suspense
    • Use generateStaticParams for static pages
  5. Security

    • Always validate server-side
    • Use CSRF protection (automatic with Server Actions)
    • Sanitize user inputs
  6. SEO

    • Use generateMetadata for dynamic meta tags
    • Implement proper Open Graph tags
    • Use semantic HTML

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/codekit
GitHub Stars
1
First Seen
10 days ago
Installed on
opencode4
antigravity4
claude-code4
github-copilot4
codex4
zencoder4