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

gs-nextjs-web-client skill

/skills/gs-nextjs-web-client

This skill guides building Next.js 15+ UI components with Clean Architecture, ensuring DTO-based data flow and correct server-client boundaries for

npx playbooks add skill gilbertopsantosjr/fullstacknextjs --skill gs-nextjs-web-client

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

Files (1)
SKILL.md
5.1 KB
---
name: gs-nextjs-web-client
description: Guide for building Next.js 15+ React 19+ frontend components with Clean Architecture. Components receive DTOs from server actions, never Entities directly. Use when creating UI components, pages, layouts, forms, or client-side interactivity.
---

# Next.js Web Client Development

## Clean Architecture Data Flow

Components receive **DTOs** from server actions, never Entities:

```
Component → Server Action → Use Case → Repository → DB
                                ↓
                              DTO (returned)
```

## Importing from Features

```typescript
// ✅ CORRECT: Import DTOs and actions
import type { CategoryDTO } from '@/features/category'
import { listCategoriesAction, createCategoryAction } from '@/features/category'

// ❌ WRONG: Never import from backend directly
import { Category } from '@/backend/domain/category/entities' // VIOLATION!
import { CreateCategoryUseCase } from '@/backend/application/category' // VIOLATION!
```

## Server vs Client Components

**Default to Server Components** unless you need interactivity:

```
components/
├── CategoryList.tsx           # Server component (default)
├── CategoryForm.tsx           # Re-exports server
├── CategoryForm-server.tsx    # Calls action, passes DTO
└── CategoryForm-client.tsx    # 'use client', handles form
```

### Server Component with Action

```typescript
// CategoryList.tsx (Server Component)
import type { CategoryDTO } from '@/features/category'
import { listCategoriesAction } from '@/features/category'

export async function CategoryList() {
  const [result, err] = await listCategoriesAction({})

  if (err) return <div>Error loading categories</div>

  return (
    <ul>
      {result.items.map((category: CategoryDTO) => (
        <li key={category.id}>{category.name}</li>
      ))}
    </ul>
  )
}
```

### Client Component with Mutation

```typescript
// CategoryForm-client.tsx
'use client'
import { useServerAction } from 'zsa-react'
import { createCategoryAction } from '@/features/category'
import type { CreateCategoryInput } from '@/features/category'

export function CategoryFormClient() {
  const { execute, isPending, error } = useServerAction(createCategoryAction)

  const onSubmit = async (data: CreateCategoryInput) => {
    const [result, err] = await execute(data)
    if (err) return toast.error(err.message)
    toast.success('Created!')
  }

  return <form onSubmit={handleSubmit(onSubmit)}>...</form>
}
```

## Forms with React Hook Form

```typescript
'use client'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { CreateCategorySchema, type CreateCategoryInput } from '@/features/category'
import { createCategoryAction } from '@/features/category'

export function CreateCategoryForm() {
  const form = useForm<CreateCategoryInput>({
    resolver: zodResolver(CreateCategorySchema),
    defaultValues: { name: '' },
  })

  const { execute, isPending } = useServerAction(createCategoryAction)

  const onSubmit = async (data: CreateCategoryInput) => {
    const [result, err] = await execute(data)
    if (err) return form.setError('root', { message: err.message })
    // Success handling
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        <FormField name="name" ... />
        <Button type="submit" disabled={isPending}>Create</Button>
      </form>
    </Form>
  )
}
```

## Data Tables with DTOs

```typescript
import type { CategoryDTO } from '@/features/category'

export function CategoryTable({ items }: { items: CategoryDTO[] }) {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Name</TableHead>
          <TableHead>Status</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {items.map((category) => (
          <TableRow key={category.id}>
            <TableCell>{category.name}</TableCell>
            <TableCell>{category.status}</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  )
}
```

## Component Naming

| Type | Case | Example |
|------|------|---------|
| Components | PascalCase | `CategoryCard.tsx` |
| Client suffix | -client | `CategoryForm-client.tsx` |
| Server suffix | -server | `CategoryForm-server.tsx` |
| Hooks | use-kebab | `use-category-form.ts` |
| Pages | kebab-case | `app/(dashboard)/categories/page.tsx` |

## Rules for Components

1. **Import DTOs** from feature index, never Entities
2. **Call actions** for data operations, never Use Cases directly
3. **Type with DTOs** - `items: CategoryDTO[]` not `items: Category[]`
4. **Handle errors** from action `[result, err]` tuple
5. **No backend imports** - features are the boundary

## Detection Commands

```bash
# Components importing from backend (VIOLATION)
grep -rn "from '@/backend/" src/features/*/components/
grep -rn "from '@/backend/" src/app/

# Components importing Entities (VIOLATION)
grep -rn "import.*Entity" src/features/*/components/
grep -rn "import.*Entity" src/app/
```

## References

- Server Actions: `skills/nextjs-server-actions/SKILL.md`
- React Query: `skills/tanstack-react-query/SKILL.md`

Overview

This skill is a practical guide for building Next.js 15+ and React 19+ frontends using Clean Architecture principles. It enforces a clear boundary: UI components receive DTOs from server actions and never import backend Entities or Use Cases directly. The guide covers component organization, server vs client components, forms, tables, naming conventions, and detection commands.

How this skill works

It defines a data flow where components call server actions and receive DTOs back, keeping application domain logic behind the feature boundary. Server components are the default for data fetching and rendering; client components are used only for interactivity and mutations, calling server actions via a client helper. The skill includes examples for forms (React Hook Form + Zod), tables, error handling, and file naming patterns to keep code consistent.

When to use it

  • Creating UI components, pages, layouts or lists that fetch and render data
  • Building interactive forms or mutation flows that require client-side behavior
  • Organizing a Next.js project to follow Clean Architecture boundaries
  • Adding tables or reusable display components typed with DTOs
  • Standardizing component and hook naming across the app

Best practices

  • Always import DTOs and actions from a feature index; never import backend Entities or Use Cases directly
  • Default to server components for rendering; create explicit -client files for interactive behavior
  • Type props with DTOs (e.g., items: CategoryDTO[]) instead of domain entities
  • Handle action responses as [result, err] tuples and surface errors to the UI
  • Follow the naming conventions: PascalCase for components, -client/-server suffixes, use-kebab for hooks, kebab-case for page folders

Example use cases

  • Server-rendered category list: server component calls listCategoriesAction and maps CategoryDTOs into a table
  • Interactive create form: a -client component uses useServerAction to call createCategoryAction and displays toast feedback
  • Form validation: use React Hook Form with zodResolver and execute server action on submit, mapping errors to form.setError
  • Reusable table component: accept DTO arrays and render consistent headers and rows
  • Detection scripts: run grep checks to find accidental imports from backend or Entity usage

FAQ

Why should components never import Entities or Use Cases?

Importing Entities or Use Cases couples UI code to backend implementation and breaks the feature boundary. DTOs and server actions provide a stable, serializable contract for the frontend.

When should I create a client component?

Create a client component when you need browser-only behavior such as event handlers, form state, or hooks. Keep data fetching in server components and delegate mutations via server actions.