Server Components
React Server Components pattern for zero-bundle server-rendered components with direct backend access
You are an expert in React Server Components (RSC) for building React applications. ## Key Points - **Client Components**: Marked with `"use client"` at the top of the file. They run on both server (for SSR) and client, and can use state, effects, event handlers, and browser APIs. - **Server Actions**: Functions marked with `"use server"` that run on the server but can be called from the client as if they were local functions — used for mutations and form handling. - Default to Server Components. Only add `"use client"` when you need interactivity, hooks, or browser APIs. - Keep Client Components as leaf nodes — push interactive elements to the smallest possible components. - Pass serializable data from Server to Client Components. Functions, classes, and Dates need conversion. - Co-locate data fetching with the component that needs it — Server Components eliminate the need for API routes for read operations. - Use streaming and Suspense to progressively render Server Components that perform slow queries. - **Adding `"use client"` too high in the tree**: This pushes all descendants into the client bundle, defeating the purpose of RSC. - **Importing server-only code in Client Components**: Database clients, API keys, and Node APIs must stay in Server Components. Use the `server-only` package to enforce this at build time. - **Passing non-serializable props**: Server-to-client boundaries serialize props via JSON. Functions, Maps, Sets, and circular references will error. - **Mutating data without revalidation**: After a Server Action mutates data, call `revalidatePath` or `revalidateTag` so the updated Server Component re-renders with fresh data.
skilldb get react-patterns-skills/Server ComponentsFull skill: 196 linesReact Server Components — React Patterns
You are an expert in React Server Components (RSC) for building React applications.
Overview
React Server Components execute exclusively on the server. They can directly access databases, file systems, and internal services without exposing that code or its dependencies to the client bundle. Server Components send a serialized UI description (not HTML) to the client, where it merges seamlessly with interactive Client Components. This architecture eliminates large dependency bundles, reduces client JavaScript, and simplifies data fetching.
Core Philosophy
React Server Components represent a fundamental rethinking of where code runs and who pays the cost. For over a decade, the default React architecture shipped every component — data-fetching logic, rendering logic, and all their dependencies — to the client. Users downloaded megabytes of JavaScript to render data that was already available on the server moments earlier. Server Components flip this model: components that do not need interactivity execute on the server and send rendered UI to the client, keeping their code and dependencies entirely out of the browser bundle.
The mental model is a clear line between "read" and "interact." Components that read data, format it, and render static markup are Server Components. Components that respond to clicks, track input state, or animate are Client Components. Most applications are overwhelmingly read-heavy — product listings, blog posts, dashboards, profile pages — which means most components can be Server Components with zero client-side JavaScript cost. The interactive pieces (a search bar, a chart with hover tooltips, a form with validation) are pushed to the leaves of the tree as small, focused Client Components.
This architecture demands a new discipline around boundaries. Data flows from Server to Client Components through serializable props. You cannot pass functions, class instances, or Dates across the boundary. Server Actions provide the reverse channel — client-initiated mutations that execute on the server. Thinking in terms of these boundaries early in the design process prevents the painful refactors that come from discovering mid-project that a deeply nested component needs to move across the server-client divide.
Core Concepts
- Server Components (default): In frameworks like Next.js App Router, every component is a Server Component unless explicitly marked otherwise. They run on the server, have zero client-side JavaScript cost, and cannot use hooks or browser APIs.
- Client Components: Marked with
"use client"at the top of the file. They run on both server (for SSR) and client, and can use state, effects, event handlers, and browser APIs. - The Boundary: A Server Component can render a Client Component, but a Client Component cannot import a Server Component directly — it can only receive one as a
childrenprop or other ReactNode prop. - Server Actions: Functions marked with
"use server"that run on the server but can be called from the client as if they were local functions — used for mutations and form handling.
Implementation Patterns
Server Component with Direct Data Access
// app/users/page.tsx — Server Component (default)
import { db } from "@/lib/db";
export default async function UsersPage() {
const users = await db.user.findMany({
orderBy: { createdAt: "desc" },
take: 50,
});
return (
<main>
<h1>Users</h1>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name} — {user.email}</li>
))}
</ul>
</main>
);
}
Mixing Server and Client Components
// app/dashboard/page.tsx — Server Component
import { db } from "@/lib/db";
import { DashboardChart } from "./DashboardChart"; // Client Component
export default async function DashboardPage() {
const metrics = await db.metric.findMany({ where: { period: "daily" } });
return (
<main>
<h1>Dashboard</h1>
{/* Server data passed as props to a Client Component */}
<DashboardChart data={metrics} />
</main>
);
}
// app/dashboard/DashboardChart.tsx
"use client";
import { useState } from "react";
import { BarChart, Bar, XAxis, YAxis } from "recharts";
interface Metric {
date: string;
value: number;
}
export function DashboardChart({ data }: { data: Metric[] }) {
const [range, setRange] = useState<"7d" | "30d">("7d");
const filtered = range === "7d" ? data.slice(-7) : data;
return (
<div>
<button onClick={() => setRange("7d")}>7 days</button>
<button onClick={() => setRange("30d")}>30 days</button>
<BarChart width={600} height={300} data={filtered}>
<XAxis dataKey="date" />
<YAxis />
<Bar dataKey="value" />
</BarChart>
</div>
);
}
Server Actions for Mutations
// app/posts/new/page.tsx — Server Component
import { redirect } from "next/navigation";
import { db } from "@/lib/db";
async function createPost(formData: FormData) {
"use server";
const title = formData.get("title") as string;
const body = formData.get("body") as string;
await db.post.create({ data: { title, body } });
redirect("/posts");
}
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" placeholder="Title" required />
<textarea name="body" placeholder="Body" required />
<button type="submit">Publish</button>
</form>
);
}
Passing Server Components as Children
// Server Component
import { Sidebar } from "./Sidebar"; // Client Component
import { NavLinks } from "./NavLinks"; // Server Component
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="flex">
{/* Server Component passed as children to Client Component */}
<Sidebar>
<NavLinks />
</Sidebar>
<main>{children}</main>
</div>
);
}
// Sidebar.tsx
"use client";
import { useState, ReactNode } from "react";
export function Sidebar({ children }: { children: ReactNode }) {
const [open, setOpen] = useState(true);
return (
<aside style={{ width: open ? 250 : 0 }}>
<button onClick={() => setOpen(!open)}>Toggle</button>
{open && children}
</aside>
);
}
Best Practices
- Default to Server Components. Only add
"use client"when you need interactivity, hooks, or browser APIs. - Keep Client Components as leaf nodes — push interactive elements to the smallest possible components.
- Pass serializable data from Server to Client Components. Functions, classes, and Dates need conversion.
- Co-locate data fetching with the component that needs it — Server Components eliminate the need for API routes for read operations.
- Use streaming and Suspense to progressively render Server Components that perform slow queries.
Common Pitfalls
- Adding
"use client"too high in the tree: This pushes all descendants into the client bundle, defeating the purpose of RSC. - Importing server-only code in Client Components: Database clients, API keys, and Node APIs must stay in Server Components. Use the
server-onlypackage to enforce this at build time. - Passing non-serializable props: Server-to-client boundaries serialize props via JSON. Functions, Maps, Sets, and circular references will error.
- Confusing Server Components with SSR: SSR renders Client Components to HTML on the server for the initial load. Server Components never run on the client at all — they are a different execution model.
- Mutating data without revalidation: After a Server Action mutates data, call
revalidatePathorrevalidateTagso the updated Server Component re-renders with fresh data.
Anti-Patterns
-
"use client" at the layout level: Marking a top-level layout or page component as a Client Component because one child needs interactivity. This forces every descendant into the client bundle, negating the benefits of Server Components. Instead, keep the layout as a Server Component and extract the interactive piece into a small Client Component leaf.
-
Fetching data in Client Components via API routes: Creating API route handlers that query the database, then calling those routes from Client Components with
useEffectandfetch. Server Components can query the database directly, eliminating the API route, the client-side fetch, the loading state, and the extra network hop. -
Passing non-serializable props across the boundary: Attempting to pass functions, Map/Set objects, Dates, or class instances from a Server Component to a Client Component. The serialization boundary silently drops or errors on these values. Convert to plain objects and primitives before crossing.
-
Server Actions that return large payloads: Using Server Actions to return bulk data instead of performing targeted mutations. Server Actions are designed for writes (create, update, delete) followed by revalidation. For reads, use Server Components that fetch data directly.
-
Treating Server Components as SSR: Assuming Server Components are the same as server-side rendering. SSR renders Client Components to HTML on the server for the initial page load, but the component code still ships to the client for hydration. Server Components never ship code to the client — they are a fundamentally different execution model.
Install this skill directly: skilldb add react-patterns-skills
Related Skills
Compound Components
Compound component pattern for building flexible, implicitly-shared React component APIs
Context Patterns
React Context patterns for efficient state sharing, provider composition, and avoiding unnecessary re-renders
Custom Hooks
Custom hooks pattern for extracting and reusing stateful logic across React components
Error Boundaries
Error boundary pattern for gracefully catching and recovering from runtime errors in React component trees
Optimistic Updates
Optimistic update patterns for instant UI feedback with server reconciliation and rollback in React
Render Props
Render props pattern for sharing cross-cutting logic through function-as-children and render callbacks