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 Child in Client WrapperInject a server component into a client wrapper.
// Server
<ClientDrawer>
<ServerFeed />
</ClientDrawer>Client Wrapper PreservationClient wrapper renders its server children slot.
'use client';
function ClientDrawer({ children }: Props) {
const [open, setOpen] = useState(false);
return <Drawer open={open}>{children}</Drawer>;
}Server Action DirectiveDeclare a server-side callable action.
async function like(id: string) {
'use server';
await db.likes.increment(id);
}RSC Migration Gotchas
Missing Client DirectiveuseState without 'use client' errors at build time.
// Error: useState needs 'use client'
function Broken() { useState(); }Browser API Guardswindow and localStorage fail on the server.
// Client only
useEffect(() => {
const w = window.innerWidth;
}, []);Third-Party WrappersWrap legacy client libraries in client files.
'use client';
export { Carousel } from 'legacy-carousel';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
Server queries the DB, then hands serialized data to a client counter.
- Mark the interactive counter with 'use client'.
- Await the DB call in the async Server Component.
- Pass only serializable props across the boundary.
// LikeBtn.tsx (client)
'use client';
export function LikeBtn({ id, initial }: Props) {
const [likes, setLikes] = useState(initial);
return (
<Button onClick={() => setLikes((c) => c + 1)}>
Likes: {likes}
</Button>
);
}
// PostView.tsx (server)
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.