server-components
npx machina-cli add skill davepoon/buildwithclaude/server-components --openclawReact Server Components in Next.js
Overview
React Server Components (RSC) allow components to render on the server, reducing client-side JavaScript and enabling direct data access. In Next.js App Router, all components are Server Components by default.
Server vs Client Components
Server Components (Default)
Server Components run only on the server:
// app/users/page.tsx (Server Component - default)
async function UsersPage() {
const users = await db.user.findMany() // Direct DB access
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
Benefits:
- Direct database/filesystem access
- Keep sensitive data on server (API keys, tokens)
- Reduce client bundle size
- Automatic code splitting
Client Components
Add 'use client' directive for interactivity:
// components/counter.tsx
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}
Use Client Components for:
useState,useEffect,useReducer- Event handlers (
onClick,onChange) - Browser APIs (
window,document) - Custom hooks with state
The Mental Model
Think of the component tree as having a "client boundary":
Server Component (page.tsx)
├── Server Component (header.tsx)
├── Client Component ('use client') ← boundary
│ ├── Client Component (child)
│ └── Client Component (child)
└── Server Component (footer.tsx)
Key rules:
- Server Components can import Client Components
- Client Components cannot import Server Components
- You can pass Server Components as
childrento Client Components
Composition Patterns
Pattern 1: Server Data → Client Interactivity
Fetch data in Server Component, pass to Client:
// app/products/page.tsx (Server)
import { ProductList } from './product-list'
export default async function ProductsPage() {
const products = await getProducts()
return <ProductList products={products} />
}
// app/products/product-list.tsx (Client)
'use client'
export function ProductList({ products }: { products: Product[] }) {
const [filter, setFilter] = useState('')
const filtered = products.filter(p =>
p.name.includes(filter)
)
return (
<>
<input onChange={e => setFilter(e.target.value)} />
{filtered.map(p => <ProductCard key={p.id} product={p} />)}
</>
)
}
Pattern 2: Children as Server Components
Pass Server Components through children prop:
// components/client-wrapper.tsx
'use client'
export function ClientWrapper({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
{isOpen && children} {/* Server Component content */}
</div>
)
}
// app/page.tsx (Server)
import { ClientWrapper } from '@/components/client-wrapper'
import { ServerContent } from '@/components/server-content'
export default function Page() {
return (
<ClientWrapper>
<ServerContent /> {/* Renders on server! */}
</ClientWrapper>
)
}
Pattern 3: Slots for Complex Layouts
Use multiple children slots:
// components/dashboard-shell.tsx
'use client'
interface Props {
sidebar: React.ReactNode
main: React.ReactNode
}
export function DashboardShell({ sidebar, main }: Props) {
const [collapsed, setCollapsed] = useState(false)
return (
<div className="flex">
{!collapsed && <aside>{sidebar}</aside>}
<main>{main}</main>
</div>
)
}
Data Fetching
Async Server Components
Server Components can be async:
// app/posts/page.tsx
export default async function PostsPage() {
const posts = await fetch('https://api.example.com/posts')
.then(res => res.json())
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Parallel Data Fetching
Fetch multiple resources in parallel:
export default async function DashboardPage() {
const [user, posts, analytics] = await Promise.all([
getUser(),
getPosts(),
getAnalytics(),
])
return (
<Dashboard user={user} posts={posts} analytics={analytics} />
)
}
Streaming with Suspense
Stream slow components:
import { Suspense } from 'react'
export default function Page() {
return (
<div>
<Header /> {/* Renders immediately */}
<Suspense fallback={<PostsSkeleton />}>
<SlowPosts /> {/* Streams when ready */}
</Suspense>
</div>
)
}
Decision Guide
Use Server Component when:
- Fetching data
- Accessing backend resources
- Keeping sensitive info on server
- Reducing client JavaScript
- Component has no interactivity
Use Client Component when:
- Using state (
useState,useReducer) - Using effects (
useEffect) - Using event listeners
- Using browser APIs
- Using custom hooks with state
Common Mistakes
- Don't add
'use client'unnecessarily - it increases bundle size - Don't try to import Server Components into Client Components
- Do serialize data at boundaries (no functions, classes, or dates)
- Do use the children pattern for composition
Resources
For detailed patterns, see:
references/server-vs-client.md- Complete comparison guidereferences/composition-patterns.md- Advanced compositionexamples/data-fetching-patterns.md- Data fetching examples
Source
git clone https://github.com/davepoon/buildwithclaude/blob/main/plugins/nextjs-expert/skills/server-components/SKILL.mdView on GitHub Overview
React Server Components (RSC) render on the server, reducing client-side JavaScript and enabling direct data access. In Next.js App Router, components are Server Components by default, with Client Components activated via the 'use client' directive. This skill helps you architect with server vs client boundaries, optimize performance, and compose components across the boundary.
How This Skill Works
Server Components run on the server and can directly access databases and files, delivering HTML to the client and shrinking the client bundle. Client Components enable interactivity and browser APIs, and must opt into the client boundary using 'use client'. Remember the rules: Server Components can import Client Components, but Client Components cannot import Server Components; you pass Server Components as children to Client Components to enable mixed rendering.
When to Use It
- When you need direct server-side data access (e.g., database queries) without exposing secrets to the client.
- When you want to minimize client-side JavaScript by rendering heavy UI on the server.
- When you require interactive UI only in specific parts of the page, guarded by Client Components.
- When you want to compose a page using server-rendered content and still support client interactivity via props.
- When managing sensitive data or API keys that should stay on the server.
Quick Start
- Step 1: Identify code that can run on the server and mark it as a Server Component.
- Step 2: Move data fetching to the server component and pass results to a client component.
- Step 3: Add 'use client' only to components that require interactivity, and verify import directions.
Best Practices
- Fetch data in Server Components whenever possible and pass it down to Client Components as props.
- Keep Client Components small and focused on interactivity (state, effects, event handlers).
- Only use 'use client' in components that truly need client-side state or browser APIs.
- Respect the import rules: Server Components can import Client Components; avoid importing Server Components into Client Components.
- Profile and test the boundary to ensure no unnecessary data exposure or extra client JS.
Example Use Cases
- Product catalog: server fetches products; a Client Component handles filtering and search.
- Blog feed: server renders list; client component adds like/bookmark interactions.
- Dashboard widgets: server-rendered data panels with a client-side dashboard for UI state.
- Header with navigation: server renders static parts; a client wrapper handles menu toggles.
- Modal forms: server-delivered content within a Client Component that manages visibility.