skills/react-server-components-expert/SKILL.md
Next.js App Router RSC architecture, Server Actions, streaming SSR, and partial hydration. Activate on: 'use server', 'use client', server components, App Router, streaming, partial hydration, Server Actions. NOT for: Pages Router (use nextjs-pages-router), client-only SPAs (use react-performance-optimizer), API routes without UI (use api-architect).
npx skillsauth add curiositech/windags-skills react-server-components-expertInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Architect Next.js App Router applications with Server Components, Server Actions, streaming SSR, and minimal client JavaScript.
Activate on: 'use server', 'use client' boundary decisions, App Router migration, streaming SSR, Server Actions for mutations, partial hydration strategy, RSC payload optimization.
NOT for: Next.js Pages Router (getServerSideProps/getStaticProps) -- use nextjs-pages-router. Client-only React SPAs -- use react-performance-optimizer. Pure API endpoints -- use api-architect.
'use client') vs. pure render (default Server Component).'use client' boundaries down -- keep them as leaf nodes, never at layout level.async/await, no useEffect.'use server' functions for mutations, form submissions, revalidation.loading.tsx and <Suspense> for progressive page rendering.| Domain | Technologies | Key Patterns |
|--------|-------------|--------------|
| Server Components | Next.js 14+, React 19 RSC | Zero-bundle server render, async components |
| Server Actions | 'use server' functions | Form mutations, revalidatePath, revalidateTag |
| Streaming SSR | <Suspense>, loading.tsx | Progressive rendering, skeleton fallbacks |
| Partial Hydration | 'use client' boundaries | Interactive islands in server-rendered pages |
| Caching | fetch() with next.revalidate, unstable_cache | ISR, on-demand revalidation, tag-based cache |
| Metadata | generateMetadata(), generateStaticParams() | Dynamic SEO, static path generation |
Push 'use client' to the smallest interactive leaf. Never mark layouts or pages as client components.
app/
layout.tsx ← Server Component (shared shell, nav, metadata)
page.tsx ← Server Component (async data fetch)
components/
ProductGrid.tsx ← Server Component (renders list)
AddToCartBtn.tsx ← 'use client' (onClick handler)
SearchFilter.tsx ← 'use client' (controlled input state)
ProductCard.tsx ← Server Component (static render)
┌─ layout.tsx (SERVER) ────────────────────┐
│ ┌─ page.tsx (SERVER) ──────────────────┐│
│ │ ┌─ ProductGrid (SERVER) ──────────┐ ││
│ │ │ ProductCard (SERVER) │ ││
│ │ │ AddToCartBtn (CLIENT) ← leaf │ ││
│ │ └─────────────────────────────────┘ ││
│ │ SearchFilter (CLIENT) ← leaf ││
│ └──────────────────────────────────────┘│
└──────────────────────────────────────────┘
Replace API routes with colocated Server Actions for type-safe mutations.
// app/products/[id]/page.tsx (Server Component)
import { revalidatePath } from 'next/cache';
import { db } from '@/lib/db';
async function addReview(formData: FormData) {
'use server';
const rating = Number(formData.get('rating'));
const comment = String(formData.get('comment'));
await db.review.create({ data: { rating, comment, productId } });
revalidatePath(`/products/${productId}`);
}
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await db.product.findUnique({ where: { id: params.id } });
return (
<div>
<h1>{product.name}</h1>
<form action={addReview}>
<input name="rating" type="number" min={1} max={5} />
<textarea name="comment" />
<button type="submit">Submit Review</button>
</form>
</div>
);
}
Wrap slow data fetches in <Suspense> so the shell renders instantly.
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { RevenueChart } from './RevenueChart';
import { RecentOrders } from './RecentOrders';
import { SkeletonChart, SkeletonTable } from '@/components/skeletons';
export default function DashboardPage() {
return (
<div className="grid grid-cols-2 gap-4">
<Suspense fallback={<SkeletonChart />}>
<RevenueChart /> {/* async Server Component, fetches own data */}
</Suspense>
<Suspense fallback={<SkeletonTable />}>
<RecentOrders /> {/* async Server Component, fetches own data */}
</Suspense>
</div>
);
}
'use client' -- forces entire subtree to be client-rendered, destroying RSC benefits. Move interactive parts to child leaf components instead.useEffect for data fetching in Server Components -- hooks do not exist in Server Components. Fetch with async/await at the component level.<Suspense> around the entire page defeats streaming. Use granular boundaries per data source.fetch() cache causes invisible bugs. Always set next: { revalidate: N } or use revalidateTag/revalidatePath after mutations.'use client' on layout or page filesasync/await in Server Components (no useEffect)'use server' directive and call revalidatePath/revalidateTag<Suspense> with skeleton fallbackgenerateMetadata() provides dynamic SEO for every pageloading.tsx exists for route segments with async datadata-ai
license: Apache-2.0 NOT for unrelated tasks outside this domain.
development
Use when designing caching strategies (cache-aside, write-through, write-behind), implementing distributed locks, building rate limiters, leaderboards, real-time streams (XADD/consumer groups), pub/sub, or tuning eviction policies. Triggers: thundering-herd on cache miss, dogpile on key expiry, Redlock vs SET-NX-PX choice, sliding-window rate limiter, hot-key on a single cluster slot, big-key blowup, MULTI/EXEC across slots, KEYS in production. NOT for Redis Cluster operations/admin (different domain), embedded KV (SQLite, leveldb), in-process LRU caches, or Memcached.
tools
Drawing the `'use client'` boundary correctly in React Server Components apps (Next.js App Router, RSC frameworks) — leaf-pushing, slot composition, serialization rules, and environment poisoning prevention. Grounded in react.dev and Next.js 16 docs.
development
Use when designing rate limiting for an API, choosing between token bucket / sliding window / leaky bucket / fixed window, implementing it in Redis, deciding edge (Cloudflare/Upstash) vs origin enforcement, sizing per-user vs per-IP vs per-endpoint quotas, returning the right 429 response with Retry-After, or fixing the boundary-burst bug in fixed-window limiters. Triggers: 429 too many requests, INCR + EXPIRE, ZADD + ZREMRANGEBYSCORE + ZCARD, X-RateLimit-Remaining header, Cloudflare WAF rate limiting rules, Upstash @upstash/ratelimit, leaky bucket shaping vs policing, distributed rate limiter consistency. NOT for DDoS mitigation specifically (different scale), CAPTCHA / bot management, full WAF design, or per-user quota billing.