Loading...
Loading...
Guides proper usage of "use client" directive in React/Next.js. Use this skill when adding client components, troubleshooting Server Component errors, or deciding where to place the client boundary.
npx skill4agent add flpbalada/my-opencode-config react-use-client-boundary"use client""use client""use client"┌─────────────────────────────────────────────────────┐
│ SERVER TERRITORY │
│ ┌─────────────┐ │
│ │ page.tsx │ (Server Component - default) │
│ │ │ │
│ │ <Header /> │───────────────────────┐ │
│ └─────────────┘ │ │
│ ▼ │
│ ════════════════ "use client" FENCE ════════════ │
│ │ │
│ ┌─────────────────────────────────────┼──────────┐ │
│ │ CLIENT TERRITORY ▼ │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Header.tsx │───▶│ NavMenu.tsx │ │ │
│ │ │"use client" │ │ (no directive│ │ │
│ │ │ │ │ needed!) │ │ │
│ │ └─────────────┘ └─────────────┘ │ │
│ │ │ │
│ │ You're already inside - no more fences needed │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘useStateuseEffectuseContextonClickonChangeonSubmitwindowdocumentlocalStorage"use client"// ❌ WRONG: Unnecessary "use client" in child
// components/form.tsx
"use client"
import { Input } from "./input"
import { Button } from "./button"
export function Form() {
const [value, setValue] = useState("")
return (
<form>
<Input value={value} onChange={setValue} />
<Button type="submit">Send</Button>
</form>
)
}
// components/input.tsx
"use client" // ❌ WRONG - already a client component!
export function Input({ value, onChange }) {
return <input value={value} onChange={e => onChange(e.target.value)} />
}
// components/button.tsx
"use client" // ❌ WRONG - already a client component!
export function Button({ children, type }) {
return <button type={type}>{children}</button>
}// ✅ CORRECT: Only the entry point has "use client"
// components/form.tsx
"use client"
import { Input } from "./input"
import { Button } from "./button"
export function Form() {
const [value, setValue] = useState("")
return (
<form>
<Input value={value} onChange={setValue} />
<Button type="submit">Send</Button>
</form>
)
}
// components/input.tsx
// ✅ No directive - imported by client component
export function Input({ value, onChange }) {
return <input value={value} onChange={e => onChange(e.target.value)} />
}
// components/button.tsx
// ✅ No directive - imported by client component
export function Button({ children, type }) {
return <button type={type}>{children}</button>
}Is this component imported by a Server Component?
│
├─ NO ──▶ Is its parent/importer a Client Component?
│ │
│ ├─ YES ──▶ ❌ Don't add "use client" (already in boundary)
│ │
│ └─ NO ───▶ Check the import chain upward
│
└─ YES ─▶ Does this component need client features?
│
├─ NO ──▶ ❌ Don't add "use client" (keep it server)
│
└─ YES ─▶ ✅ Add "use client" (create boundary here)// app/products/page.tsx (Server Component - no directive)
import { ProductList } from "@/components/product-list"
import { SearchFilters } from "@/components/search-filters"
import { getProducts } from "@/lib/api"
export default async function ProductsPage() {
const products = await getProducts() // Server-side data fetching
return (
<main>
<h1>Products</h1>
<SearchFilters /> {/* Client boundary starts here */}
<ProductList data={products} /> {/* Server component */}
</main>
)
}
// components/search-filters.tsx
"use client" // ✅ Boundary: imported by server, needs state
import { FilterDropdown } from "./filter-dropdown"
import { PriceSlider } from "./price-slider"
export function SearchFilters() {
const [filters, setFilters] = useState({})
return (
<div>
<FilterDropdown onSelect={...} /> {/* No directive needed */}
<PriceSlider onChange={...} /> {/* No directive needed */}
</div>
)
}
// components/filter-dropdown.tsx
// ✅ No "use client" - already inside client boundary
export function FilterDropdown({ onSelect }) {
return <select onChange={e => onSelect(e.target.value)}>...</select>
}
// components/price-slider.tsx
// ✅ No "use client" - already inside client boundary
export function PriceSlider({ onChange }) {
return <input type="range" onChange={e => onChange(e.target.value)} />
}// components/card.tsx
// No directive - works in both contexts if it's pure presentation
export function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
)
}
// app/page.tsx (Server Component)
import { Card } from "@/components/card"
// Card renders as server component here
// components/modal.tsx
"use client"
import { Card } from "@/components/card"
// Card renders as client component here (inside boundary)"use client""use client"| Do | Don't |
|---|---|
Place | Sprinkle |
| Keep the client boundary as small as possible | Make entire pages client components |
| Let child components inherit client context | Add redundant |
| Use server components for data fetching | Fetch data in client components when avoidable |