Agentient

rsc-composition-patterns

React Server Components architecture patterns for Next.js 14+ with server-first development. PROACTIVELY activate for: (1) deciding Server vs Client Component boundaries, (2) composing components with 'use client', (3) managing interactivity patterns. Triggers: "server component", "client component", "'use client'"

Agentient 2 1 Updated 4mo ago
GitHub

Install

npx skillscat add agentient/vibekit/rsc-composition-patterns

Install via the SkillsCat registry.

SKILL.md

React Server Components Composition Patterns

Core Principle: Server-First

DEFAULT: All components are Server Components unless they need:

  • State (useState, useReducer)
  • Effects (useEffect, useLayoutEffect)
  • Event handlers (onClick, onChange)
  • Browser APIs (window, localStorage)
  • React hooks (custom hooks that use the above)

The 'use client' Boundary Rule

// BAD: 'use client' at top level (forces entire tree to be client-rendered)
// app/dashboard/page.tsx
'use client'
export default function Dashboard() {
  const [count, setCount] = useState(0);
  return <div>{/* entire page is now client-rendered */}</div>;
}

// GOOD: Server Component at top, Client Component at leaf
// app/dashboard/page.tsx (Server Component - no 'use client')
import { Counter } from './Counter';
export default async function Dashboard() {
  const data = await fetchData(); // Server-side data fetching
  return (
    <div>
      <h1>Dashboard</h1>
      <StaticContent data={data} /> {/* Server Component */}
      <Counter /> {/* Small Client Component for interactivity */}
    </div>
  );
}

// app/dashboard/Counter.tsx (Client Component - only this is client-rendered)
'use client'
import { useState } from 'react';
export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Composition Pattern: Pass Server Components as Children

Server Components can be passed as children to Client Components:

// ClientWrapper.tsx (Client Component)
'use client'
export function ClientWrapper({ children }: { children: React.ReactNode }) {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
      {isOpen && children}
    </div>
  );
}

// page.tsx (Server Component)
export default async function Page() {
  const data = await fetchServerData();
  return (
    <ClientWrapper>
      {/* This entire subtree renders on the server! */}
      <ServerRenderedContent data={data} />
    </ClientWrapper>
  );
}

Why this works: children is passed as a serialized prop (already rendered on server).

Anti-Patterns to Avoid

Importing Server Components into Client Components:

// ERROR: Cannot import Server Component into Client Component
'use client'
import { ServerComponent } from './ServerComponent'; // ERROR!

export function ClientComponent() {
  return <ServerComponent />; // This will not work
}

Passing Non-Serializable Props:

// ERROR: Cannot pass functions from Server to Client Component
export default function ServerPage() {
  const handleClick = () => console.log('clicked');
  return <ClientButton onClick={handleClick} />; // ERROR!
}

// GOOD: Define functions inside Client Component
'use client'
export function ClientButton() {
  const handleClick = () => console.log('clicked'); // OK
  return <button onClick={handleClick}>Click</button>;
}

Quick Decision Tree

Need interactivity (state, events, hooks)?
├─ YES → Client Component ('use client')
├─ NO → Server Component (default)

Can you split the interactive part into a smaller component?
├─ YES → Keep parent as Server Component, make leaf a Client Component
├─ NO → Use Client Component but minimize its scope

Performance Benefits

Server Components:

  • Zero JavaScript sent to client (smaller bundles)
  • Direct database/API access (no extra round-trip)
  • Automatic code splitting
  • Better SEO (pre-rendered HTML)

Client Components (use sparingly):

  • Required for interactivity
  • Adds JavaScript to bundle
  • Requires serialization of props

For advanced patterns and edge cases, see resources/advanced-rsc-patterns.md.