Babysitter react-server-components

React Server Components patterns including streaming, data fetching, client/server component composition, and performance optimization.

install
source · Clone the upstream repo
git clone https://github.com/a5c-ai/babysitter
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/a5c-ai/babysitter "$T" && mkdir -p ~/.claude/skills && cp -r "$T/library/specializations/web-development/skills/react-server-components" ~/.claude/skills/a5c-ai-babysitter-react-server-components && rm -rf "$T"
manifest: library/specializations/web-development/skills/react-server-components/SKILL.md
source content

React Server Components Skill

Expert assistance for implementing React Server Components with proper patterns and composition strategies.

Capabilities

  • Design server/client component boundaries
  • Implement streaming with Suspense
  • Handle data fetching in server components
  • Compose server and client components
  • Optimize performance with selective hydration
  • Manage server actions for mutations

Usage

Invoke this skill when you need to:

  • Structure RSC application architecture
  • Implement streaming data fetching
  • Determine server vs client components
  • Pass server data to client components
  • Build server actions for forms

Inputs

ParameterTypeRequiredDescription
componentTypestringYesserver, client, shared
dataFetchingbooleanNoComponent fetches data
interactivityarrayNoRequired client interactions
streamingbooleanNoEnable streaming

Configuration Example

{
  "componentType": "server",
  "dataFetching": true,
  "streaming": true,
  "childComponents": [
    { "name": "InteractiveChart", "type": "client" }
  ]
}

Component Patterns

Server Component (Default)

// app/users/page.tsx - Server Component by default
import { db } from '@/lib/db';
import { UserCard } from './user-card';

async function getUsers() {
  return db.user.findMany({
    include: { posts: true },
  });
}

export default async function UsersPage() {
  const users = await getUsers();

  return (
    <div className="grid gap-4">
      <h1>Users</h1>
      {users.map((user) => (
        <UserCard key={user.id} user={user} />
      ))}
    </div>
  );
}

Client Component

// components/interactive-counter.tsx
'use client';

import { useState } from 'react';

export function InteractiveCounter({ initialCount }: { initialCount: number }) {
  const [count, setCount] = useState(initialCount);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>
        Increment
      </button>
    </div>
  );
}

Composition Pattern

// app/dashboard/page.tsx - Server Component
import { Suspense } from 'react';
import { db } from '@/lib/db';
import { InteractiveChart } from '@/components/interactive-chart';
import { ChartSkeleton } from '@/components/skeletons';

async function getAnalytics() {
  return db.analytics.findMany({
    orderBy: { date: 'desc' },
    take: 30,
  });
}

export default async function Dashboard() {
  const analytics = await getAnalytics();

  return (
    <div>
      <h1>Dashboard</h1>
      {/* Pass server data to client component */}
      <Suspense fallback={<ChartSkeleton />}>
        <InteractiveChart data={analytics} />
      </Suspense>
    </div>
  );
}

// components/interactive-chart.tsx - Client Component
'use client';

import { useState } from 'react';
import { LineChart } from 'recharts';

interface ChartProps {
  data: AnalyticsData[];
}

export function InteractiveChart({ data }: ChartProps) {
  const [timeRange, setTimeRange] = useState('7d');

  const filteredData = filterByTimeRange(data, timeRange);

  return (
    <div>
      <select
        value={timeRange}
        onChange={(e) => setTimeRange(e.target.value)}
      >
        <option value="7d">Last 7 days</option>
        <option value="30d">Last 30 days</option>
      </select>
      <LineChart data={filteredData} />
    </div>
  );
}

Streaming with Suspense

// app/page.tsx
import { Suspense } from 'react';
import { SlowComponent, FastComponent } from '@/components';

export default function Page() {
  return (
    <div>
      {/* Fast content renders immediately */}
      <FastComponent />

      {/* Slow content streams in */}
      <Suspense fallback={<p>Loading slow content...</p>}>
        <SlowComponent />
      </Suspense>
    </div>
  );
}

// components/slow-component.tsx
async function getSlowData() {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return { data: 'Slow data loaded' };
}

export async function SlowComponent() {
  const data = await getSlowData();
  return <div>{data.data}</div>;
}

Server Actions

// app/actions.ts
'use server';

import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';

export async function createUser(formData: FormData) {
  const name = formData.get('name') as string;
  const email = formData.get('email') as string;

  await db.user.create({
    data: { name, email },
  });

  revalidatePath('/users');
}

export async function updateUser(id: string, formData: FormData) {
  const name = formData.get('name') as string;

  await db.user.update({
    where: { id },
    data: { name },
  });

  revalidatePath(`/users/${id}`);
}

// components/user-form.tsx
'use client';

import { useFormStatus } from 'react-dom';
import { createUser } from '@/app/actions';

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Creating...' : 'Create User'}
    </button>
  );
}

export function UserForm() {
  return (
    <form action={createUser}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <SubmitButton />
    </form>
  );
}

Passing Server Components as Props

// Pattern: Server Component as children
// app/page.tsx
import { ClientWrapper } from '@/components/client-wrapper';
import { ServerContent } from '@/components/server-content';

export default function Page() {
  return (
    <ClientWrapper>
      <ServerContent />
    </ClientWrapper>
  );
}

// components/client-wrapper.tsx
'use client';

export function ClientWrapper({ children }: { children: React.ReactNode }) {
  const [isOpen, setIsOpen] = useState(true);

  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
      {isOpen && children}
    </div>
  );
}

Decision Framework

Use Server Components When:

  • Fetching data
  • Accessing backend resources directly
  • Keeping sensitive information on server
  • Keeping large dependencies on server
  • No interactivity needed

Use Client Components When:

  • Using useState, useEffect, useReducer
  • Using browser APIs
  • Adding event listeners
  • Using custom hooks with state
  • Using React Context providers

Best Practices

  • Default to Server Components
  • Push client boundaries down the tree
  • Pass serializable props to client components
  • Use Suspense for streaming
  • Colocate data fetching with components

Target Processes

  • nextjs-full-stack
  • react-server-components-migration
  • performance-optimization
  • streaming-implementation