home / skills / gilbertopsantosjr / fullstacknextjs / nextjs-web-client

nextjs-web-client skill

/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-client

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
4.2 KB
---
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)` |

Overview

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.

How this skill works

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 to use it

  • Building pages, layouts, or route-level UI with App Router
  • Creating interactive forms that need browser hooks or event handlers
  • Integrating Shadcn UI components into a component library
  • Rendering data tables with actions and compact layouts
  • Implementing lazy-loaded UI parts to reduce bundle size

Best practices

  • Default to server components and create a small client wrapper only when you need state, effects, or DOM APIs
  • Use a clear file naming pattern: Component-server.tsx and Component-client.tsx to signal responsibilities
  • Install and customize Shadcn UI in src/components/ui so you can tweak tokens and styles centrally
  • Use react-hook-form + zodResolver for typed validation and minimal rerenders in client forms
  • Prefer dynamic imports for dialogs, tabs, and heavy UI that is conditionally shown
  • Add route-level loading.tsx and error.tsx to give consistent UX and enable Suspense boundaries

Example use cases

  • Account form: fetch user data in server component and mount a client form for edits
  • Settings page: load large settings tabs dynamically when the user opens them
  • Admin table: render paginated rows with Edit actions and small client modals
  • Authentication layout: use route groups for (auth) and (dashboard) to separate concerns
  • Reusable UI library: drop Shadcn components into src/components/ui and extend them project-wide

FAQ

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.