← Back to blog

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.

March 5, 2026200 words