React Server Components
Run components exclusively on the server for zero client bundle sizes, direct database access, and seamless streaming.
TL;DR
- Render components exclusively on the backend with
RSC. - Eliminate client bundle overhead with zero-size
servercomponents. - Declare interactive boundaries clearly using the
'use client'directive.
Server vs Client Architecture
Async Server ComponentExecute async backend queries directly in component body.
export async function ProductCatalog() {
const products = await db.products.findMany();
return <ListView items={products} />;
}Direct Database QueryAccess database or ORM without intermediate REST API.
export async function UserCard({ id }: Props) {
const user = await db.user.findUnique({
where: { id },
});
return <CardTitle>{user?.name}</CardTitle>;
}Zero Bundle Size MarkdownParse large markdown text without sending parser.
import { marked } from 'marked';
export function DocView({ raw }: { raw: string }) {
const html = marked.parse(raw);
return <CardView>{html}</CardView>;
}The 'use client' Boundary
Declaring Client ComponentMark file as client entry point with directive.
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<Button onClick={() => setCount((c) => c + 1)} />
);
}Passing Serializable PropsPass primitives and plain objects across boundary.
// Allowed: strings, numbers, arrays, plain objects
<ClientCard user={{ id: '1', name: 'Sam' }} />Disallowed Boundary PropsFunctions and class instances cannot cross boundary.
// BAD: Functions cannot be serialized
<ClientCard onClick={() => console.log()} />RSC Composition Patterns
Server Component as ChildrenInject server component into client wrapper.
// App (Server)
<ClientDrawer>
<ServerFeed />
</ClientDrawer>Client Wrapper PreservationClient component renders server children slot.
'use client';
export function ClientDrawer({ children }: Props) {
const [open, setOpen] = useState(false);
return <Drawer open={open}>{children}</Drawer>;
}Server Action DirectiveDeclare server-side callable action with 'use server'.
async function likePost(id: string) {
'use server';
await db.likes.increment(id);
}RSC Migration Gotchas
Missing Client DirectiveCalling useState without 'use client' throws build error.
// Error: useState only works in Client Components
export function Broken() { useState(); }Browser API GuardsAccessing window or localStorage fails on server.
// Run only on client
useEffect(() => {
const w = window.innerWidth;
}, []);Third-Party Component WrappersWrap legacy client libraries in client files.
'use client';
export { Carousel } from 'legacy-carousel-lib';Tips
- Keep heavy dependencies like markdown parsers and database drivers inside Server Components to avoid shipping megabytes to the
browser. - Pass Server Components as
childreninto Client Components so the server components render on the server without being bundled.
Warnings
- Server Components cannot use state hooks like
useStateor attach event handlers likeonClickbecause they never run in browser DOM. - All props passed across the server-to-client boundary must be strictly
serializableinto valid JSON-compatible values or primitive records.
In Practice
Fetch database records in a Server Component and pass serialized data into an interactive Client Component counter.
- Create async Server Component querying database records directly.
- Declare interactive Client Component with 'use client' directive.
- Pass serializable database records across boundary as props.
- Mount interactive controls within the client component.
type Props = { id: string; initial?: number };
// ClientComponent.tsx
'use client';
export function LikeBtn({ id, initial }: Props) {
const [likes, setLikes] = useState(initial);
const add = () => setLikes((c) => c + 1);
return <Button onClick={add}>Likes: {likes}</Button>;
}
// ServerComponent.tsx
export async function PostView({ id }: Props) {
const post = await db.post.find(id);
return (
<CardView>
<CardTitle>{post.title}</CardTitle>
<LikeBtn id={id} initial={post.likeCount} />
</CardView>
);
}FAQ
Server components execute exclusively on the server, generating zero client JavaScript bundle size while enabling direct access to databases and backend filesystems.
Add 'use client' at the top of a file only when a component requires browser APIs, state hooks (useState, useReducer), effects (useEffect), or DOM event listeners.
Yes. Server Components can freely import and render Client Components, passing serializable data and promises down as props.