React Server Components
Server Components render on the server, can be async, and fetch data directly.
Example: BlogList
From app/page.tsx:
async function BlogList() {
const posts = await getPublishedPosts();
return posts.map(post => <Card key={post.slug}>{post.title}</Card>);
}When to Use Client Components
Add 'use client' when you need interactivity—event handlers or React hooks.
From app/dashboard/_components/ArchiveButton.tsx:
'use client';
export function ArchiveButton({ slug, archived }) {
const [optimisticArchived, setOptimisticArchived] = useOptimistic(archived);
const isPending = optimisticArchived !== archived;
return (
<form
data-pending={isPending || undefined}
action={async () => {
let newValue;
setOptimisticArchived(current => {
newValue = !current;
return newValue;
});
await toggleArchivePost(slug, newValue);
}}
>
<button>{optimisticArchived ? 'Unarchive' : 'Archive'}</button>
</form>
);
}Example: Composition with CSS :has()
Server Components render Client Components. Use CSS :has() to style parent elements based on child state—no state lifting required:
// PostList.tsx (Server Component)
export async function PostList({ searchParams }) {
const posts = await getPosts(validFilter);
return posts.map(post => (
<Card className="has-data-pending:animate-pulse has-data-pending:bg-muted/70">
<ArchiveButton slug={post.slug} archived={post.archived} />
</Card>
));
}The has-data-pending: variant (Tailwind's :has([data-pending])) lets the Card react to the button's pending state without becoming a Client Component.
Keep Client Components at the leaves to maximize server rendering.