Next.js App Skill
Description
Generate Next.js 14+ App Router components with server/client patterns.
Trigger
-
/nextjs command
-
User requests Next.js code
-
User needs App Router patterns
Prompt
You are a Next.js expert that creates modern App Router applications.
Server Component with Data Fetching
// app/users/page.tsx import { Suspense } from 'react';
interface User { id: string; name: string; email: string; }
async function getUsers(): Promise<User[]> { const res = await fetch('https://api.example.com/users', { next: { revalidate: 60 }, // ISR: revalidate every 60 seconds }); if (!res.ok) throw new Error('Failed to fetch users'); return res.json(); }
async function UserList() { const users = await getUsers();
return ( <ul className="space-y-2"> {users.map(user => ( <li key={user.id} className="p-4 bg-white rounded-lg shadow"> <h3 className="font-semibold">{user.name}</h3> <p className="text-gray-600">{user.email}</p> </li> ))} </ul> ); }
export default function UsersPage() { return ( <main className="container mx-auto p-4"> <h1 className="text-2xl font-bold mb-4">Users</h1> <Suspense fallback={<div>Loading users...</div>}> <UserList /> </Suspense> </main> ); }
Client Component with Form
'use client';
import { useState, useTransition } from 'react'; import { useRouter } from 'next/navigation'; import { createUser } from './actions';
export function CreateUserForm() { const router = useRouter(); const [isPending, startTransition] = useTransition(); const [error, setError] = useState<string | null>(null);
async function handleSubmit(formData: FormData) { setError(null); startTransition(async () => { const result = await createUser(formData); if (result.error) { setError(result.error); } else { router.push('/users'); router.refresh(); } }); }
return ( <form action={handleSubmit} className="space-y-4"> {error && <div className="text-red-600">{error}</div>}
<input
name="name"
placeholder="Name"
required
className="w-full p-2 border rounded"
/>
<input
name="email"
type="email"
placeholder="Email"
required
className="w-full p-2 border rounded"
/>
<button
type="submit"
disabled={isPending}
className="w-full p-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
{isPending ? 'Creating...' : 'Create User'}
</button>
</form>
); }
Server Action
// app/users/actions.ts 'use server';
import { revalidatePath } from 'next/cache'; import { z } from 'zod';
const CreateUserSchema = z.object({ name: z.string().min(1), email: z.string().email(), });
export async function createUser(formData: FormData) { const validated = CreateUserSchema.safeParse({ name: formData.get('name'), email: formData.get('email'), });
if (!validated.success) { return { error: 'Invalid input' }; }
try { await fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(validated.data), });
revalidatePath('/users');
return { success: true };
} catch { return { error: 'Failed to create user' }; } }
Middleware
// middleware.ts import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) { const token = request.cookies.get('token')?.value;
// Protect dashboard routes if (request.nextUrl.pathname.startsWith('/dashboard')) { if (!token) { return NextResponse.redirect(new URL('/login', request.url)); } }
return NextResponse.next(); }
export const config = { matcher: ['/dashboard/:path*'], };
Tags
nextjs , react , app-router , server-components , fullstack
Compatibility
-
Codex: ✅
-
Claude Code: ✅