Design and implement React Server Components with Next.js 15 App Router...
React Server Components (RSC) represent a paradigm shift in React architecture, enabling server-first rendering with client-side interactivity. This skill provides comprehensive patterns, templates, and best practices for building modern Next.js 15 applications using the App Router with Server Components, Server Actions, and streaming.
When to use this skill:
RSC fundamentally changes how we think about React applications:
Server Components (default):
async and use awaitClient Components (with 'use client'):
useState, useEffect, etc.)asyncKey Rule: Server Components can render Client Components, but Client Components cannot directly import Server Components (use children prop instead).
Detailed Patterns: See references/component-patterns.md for:
Next.js extends the fetch API with powerful caching and revalidation:
// Static (cached indefinitely)
await fetch(url, { cache: 'force-cache' })
// Revalidate every 60 seconds
await fetch(url, { next: { revalidate: 60 } })
// Always fresh
await fetch(url, { cache: 'no-store' })
// Tag-based revalidation
await fetch(url, { next: { tags: ['posts'] } })
Patterns:
Promise.all([fetch1, fetch2, fetch3])Detailed Implementation: See references/data-fetching.md for:
revalidatePath, revalidateTag)Server Actions enable mutations without API routes:
// app/actions.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const post = await db.post.create({ data: { title } })
revalidatePath('/posts')
redirect(`/posts/${post.id}`)
}
Progressive Enhancement: Forms work without JavaScript, then enhance with client-side states.
Detailed Implementation: See references/server-actions.md for:
Stream components independently for better perceived performance:
import { Suspense } from 'react'
export default function Dashboard() {
return (
<div>
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
<Suspense fallback={<InvoicesSkeleton />}>
<LatestInvoices />
</Suspense>
</div>
)
}
Benefits:
Templates: Use templates/ServerComponent.tsx for streaming patterns
Parallel Routes: Render multiple pages simultaneously
app/
@team/page.tsx
@analytics/page.tsx
layout.tsx # Receives both as props
Intercepting Routes: Show modals while preserving URLs
app/
photos/[id]/page.tsx # Direct route
(..)photos/[id]/page.tsx # Intercepted (modal)
Partial Prerendering (PPR): Mix static and dynamic content
export const experimental_ppr = true
// Static shell + dynamic Suspense boundaries
Detailed Implementation: See references/routing-patterns.md for:
Use grep to find specific patterns in references:
# Find component patterns
grep -r "Server Component" references/
# Search for data fetching strategies
grep -A 10 "Caching Strategies" references/data-fetching.md
# Find Server Actions examples
grep -B 5 "Progressive Enhancement" references/server-actions.md
# Locate routing patterns
grep -n "Parallel Routes" references/routing-patterns.md
# Search migration guide
grep -i "pages router\|getServerSideProps" references/migration-guide.md
children to Client ComponentsgenerateStaticParams for static routesUse provided templates for common patterns:
templates/ServerComponent.tsx - Basic async Server Component with data fetchingtemplates/ClientComponent.tsx - Interactive Client Component with hookstemplates/ServerAction.tsx - Server Action with validation and revalidationSee examples/blog-app/ for a full implementation:
See checklists/rsc-implementation.md for comprehensive validation covering:
// app/search/page.tsx
export default async function SearchPage({
searchParams,
}: {
searchParams: { q?: string }
}) {
const query = searchParams.q || ''
const results = query ? await searchProducts(query) : []
return (
<div>
<SearchForm initialQuery={query} />
<SearchResults results={results} />
</div>
)
}
import { cookies } from 'next/headers'
export default async function DashboardPage() {
const token = cookies().get('token')?.value
const user = await verifyToken(token)
if (!user) {
redirect('/login')
}
return <Dashboard user={user} />
}
'use client'
import { useOptimistic } from 'react'
export function TodoList({ todos }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo) => [...state, newTodo]
)
return <ul>{/* render optimisticTodos */}</ul>
}
Incremental Adoption: Both pages/ and app/ can coexist
Key Changes:
getServerSideProps → async Server ComponentgetStaticProps → async Server Component with caching_app.tsx → layout.tsx<Head> → generateMetadata functionDetailed Migration: See references/migration-guide.md for:
Error: "You're importing a component that needs useState"
'use client' directive to the componentError: "async/await is not valid in non-async Server Components"
async to function declarationError: "Cannot use Server Component inside Client Component"
children prop instead of importingError: "Hydration mismatch"
'use client' for components using Date.now(), Math.random(), or browser APIsAfter mastering React Server Components: