home / skills / gilbertopsantosjr / fullstacknextjs / 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-clientReview the files below or copy the command above to add this skill to your agents.
---
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`
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.
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.
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.