home / skills / gilbertopsantosjr / fullstacknextjs / nextjs-web-client
/skills/nextjs-web-client
This skill guides building Next.js 15+ client components with App Router, Shadcn UI, and React Hook Form for interactive UI.
npx playbooks add skill gilbertopsantosjr/fullstacknextjs --skill nextjs-web-clientReview the files below or copy the command above to add this skill to your agents.
---
name: nextjs-web-client
description: Guide for building Next.js 15+ React 19+ frontend components with App Router, Shadcn UI, and React Hook Form. Use when creating UI components, pages, layouts, forms, data tables, or client-side interactivity in a Next.js application.
---
# Next.js Web Client Development
## Server vs Client Components
**Default to Server Components** unless you need:
- Event handlers (onClick, onChange)
- Browser APIs (window, localStorage)
- React hooks (useState, useEffect)
### Component Split Pattern
For components needing server data + client interactivity:
```
components/
├── AccountForm.tsx # Re-exports server component
├── AccountForm-server.tsx # Fetches data, passes to client
└── AccountForm-client.tsx # 'use client', handles interactions
```
```typescript
// AccountForm-server.tsx
import { AccountFormClient } from './AccountForm-client'
export async function AccountForm() {
const data = await fetchData()
return <AccountFormClient data={data} />
}
// AccountForm-client.tsx
'use client'
export function AccountFormClient({ data }: Props) {
const [state, setState] = useState(data)
return <form>...</form>
}
```
## Shadcn UI
Install components via CLI:
```bash
npx shadcn@latest add button input form dialog table card
```
Components copy to `src/components/ui/` - customize directly.
## Forms with React Hook Form
```typescript
'use client'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
export function CreateForm() {
const form = useForm<FormInput>({
resolver: zodResolver(FormSchema),
defaultValues: { name: '' },
})
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl><Input {...field} /></FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit">Submit</Button>
</form>
</Form>
)
}
```
## Dynamic Imports
Use for conditionally rendered components:
```typescript
import dynamic from 'next/dynamic'
const EditDialog = dynamic(() => import('./EditDialog'))
const SettingsTab = dynamic(() => import('./tabs/SettingsTab'))
// Only load when needed
{showEdit && <EditDialog />}
{tab === 'settings' && <SettingsTab />}
```
## Loading & Error States
```typescript
// loading.tsx (in route folder)
export default function Loading() {
return <Skeleton className="h-[200px]" />
}
// error.tsx (in route folder)
'use client'
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<p>Something went wrong</p>
<Button onClick={reset}>Try again</Button>
</div>
)
}
// With Suspense
<Suspense fallback={<LoadingSkeleton />}>
<AsyncComponent />
</Suspense>
```
## Data Tables
```typescript
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
export function DataTable({ items }: { items: Item[] }) {
return (
<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Status</TableHead>
<TableHead className="w-[100px]">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{items.map((item) => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell>{item.status}</TableCell>
<TableCell><Button size="sm">Edit</Button></TableCell>
</TableRow>
))}
</TableBody>
</Table>
)
}
```
## Naming Conventions
| Type | Case | Example |
|------|------|---------|
| Components | PascalCase | `AccountCard.tsx` |
| Hooks | use-kebab | `use-account-form.ts` |
| Pages | kebab-case dirs | `app/(dashboard)/accounts/page.tsx` |
| Route groups | (parentheses) | `(auth)`, `(dashboard)` |
This skill provides a concise guide for building Next.js 15+ React 19+ front-end components using the App Router, Shadcn UI, and React Hook Form. It focuses on practical patterns for composing server and client components, form handling, dynamic imports, loading/error states, and data tables. Use it to accelerate reliable, maintainable UI development in modern Next.js applications.
The guidance inspects common app structure and prescribes a component-split pattern: default to server components and isolate client-only interactivity in a ‘use client’ component that receives server data. It shows how to install and customize Shadcn UI components, integrate React Hook Form with Zod validation, lazily load pieces with dynamic imports, and implement loading and error UI for route-level Suspense. Examples include a client/server split form, table rendering, and route loading/error files.
When should I choose a server component over a client component?
Prefer server components by default. Choose client components only when you need event handlers, browser APIs, or React client hooks like useState/useEffect.
How do I structure a component that needs server data and client interactivity?
Use the component split pattern: AccountForm-server.tsx fetches data and renders AccountForm-client.tsx which is marked 'use client' and handles interactions.