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 datatools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.