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.
Install
npx skillscat add gilbertopsantosjr/fullstacknextjs/gs-nextjs-web-client Install via the SkillsCat registry.
SKILL.md
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
// ✅ 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 formServer Component with Action
// 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
// 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
'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
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
- Import DTOs from feature index, never Entities
- Call actions for data operations, never Use Cases directly
- Type with DTOs -
items: CategoryDTO[]notitems: Category[] - Handle errors from action
[result, err]tuple - No backend imports - features are the boundary
Detection Commands
# 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