React

React Server Components👨‍💻

React Server Components flip the default: your components run on the server unless you explicitly opt into the client with 'use client'. This means you can query databases, read files, and use heavy libraries in your components without shipping a single byte to the browser. The tradeoff is a new mental model around serialization boundaries, composition rules, and knowing exactly where your code executes.

Key Takeaways

  • 1Every component is a Server Component by default in RSC environments like Next.js App Router — `'use client'` is the opt-in, not the default
  • 2Server Components can be `async` functions that `await` data directly — no `useEffect`, no loading state boilerplate, no API routes to wire up
  • 3Server Component code never reaches the client bundle. Import a 200KB markdown parser? Zero impact on bundle size
  • 4Client Components cannot import Server Components directly — pass them as `children` or props from a Server Component parent instead
  • 5Server Actions (`'use server'`) let you call server-side functions from forms and event handlers without building REST endpoints
  • 6Suspense boundaries define streaming chunks — the shell renders instantly, and each `<Suspense>` section streams in as its data resolves

Master react server components

Take the React Server Components course with hands-on lessons and challenges.

Examples

Async Server Component — direct database access

tsx

This component queries Prisma directly with a join across three tables. The entire function, including the db import and query, stays on the server. The browser receives only the rendered HTML. No API route, no fetch call, no loading state management.

Pushing 'use client' to the leaf — keep the boundary small

tsx

The page itself is a Server Component that fetches data. Only the interactive EnrollButton is a Client Component. This keeps the client bundle minimal — the course description, section list, and database query code all stay on the server.

Server Actions for form mutations with validation

tsx

The Server Action validates input with Zod, authenticates the user, updates the database, and revalidates the cache — all in one function. No API route to maintain. The form works with progressive enhancement: it submits even before JavaScript loads.

Streaming with Suspense — progressive page loading

tsx

Each Suspense boundary defines an independent streaming chunk. The dashboard shell renders immediately with skeleton placeholders. LearningProgress might resolve in 100ms, while the Leaderboard query takes 400ms — but neither blocks the other. Users see content progressively instead of staring at a blank page.

Passing Server Components as children to Client Components

tsx

The Providers component is a Client Component (it uses hooks and context), but its children can still be Server Components. The RootLayout renders Sidebar on the server, and the serialized output gets passed through Providers. This is the standard pattern for wrapping your app with context providers without forcing the entire tree to be client-side.

Optimistic UI with useOptimistic and Server Actions

tsx

The bookmark toggles instantly in the UI via useOptimistic while the Server Action runs in the background. If the action fails, React automatically rolls back to the real state. This pattern gives users instant feedback without sacrificing data consistency.

Common Mistakes

Mistake:

Adding `'use client'` at the top of a page component because it needs one interactive element — this pulls the entire page and all its imports into the client bundle

Fix:

Extract only the interactive part into a separate Client Component. Keep the page as a Server Component and render the Client Component as a child. A page with a like button doesn't need to be a Client Component — only the button does.

Mistake:

Trying to import a Server Component inside a Client Component — e.g. `import ServerChart from './ServerChart'` inside a `'use client'` file

Fix:

Pass the Server Component as `children` or a prop from a Server Component parent. The server renders it first and sends the serialized output to the Client Component. This is the composition pattern, not an import.

Mistake:

Passing non-serializable values (functions, class instances, Dates, Maps) as props from a Server Component to a Client Component

Fix:

Only serializable data crosses the server/client boundary: strings, numbers, booleans, plain objects, arrays, null, and a few React types. Convert Dates to ISO strings, Maps to plain objects, and move callback functions into Client Components or use Server Actions.

Mistake:

Trusting closure values in Server Actions for authorization — e.g. closing over a `userId` prop and using it to delete a record without re-verifying on the server

Fix:

Closure values in Server Actions are serialized, sent to the client, and sent back. A malicious user can tamper with them. Always re-authenticate inside the action: call `auth()` or `getSession()` to get the current user's identity from the server.

Best Practices

  • Push `'use client'` as far down the component tree as possible — the higher the boundary, the more code you ship to the browser. Leaf components like buttons, forms, and tooltips are ideal Client Components
  • Use `Promise.all()` for independent data fetches in the same Server Component instead of sequential `await` calls — parallel fetches reduce total wait time to the slowest query, not the sum of all queries
  • Wrap slow async components in `<Suspense>` boundaries so they stream independently and don't block faster parts of the page from rendering
  • Validate all Server Action inputs with Zod or a similar library — Server Actions are public HTTP endpoints, and a malicious client can send any payload
  • Use `cache()` from React to deduplicate expensive database queries when multiple Server Components in the same request need the same data
  • Keep the serialization boundary clean: pass only the data a Client Component actually needs as primitive props, not entire database models with internal fields

Summary

React Server Components run on the server by default and ship zero JavaScript to the browser. Add `'use client'` only when a component needs hooks, event handlers, or browser APIs. Async Server Components fetch data directly with `await` — no useEffect, no API routes. Server Actions handle mutations through `'use server'` functions that you can call from forms and event handlers. Suspense boundaries enable streaming, so each section of a page loads independently. The key discipline is keeping the client boundary as small as possible and understanding what can cross the serialization boundary between server and client.

Practice React with hands-on challenges

Learn react server components hands-on in your IDE

Interactive lessons and challenges on Stanza, practice in VS Code, Cursor, or the web.

Related Concepts

Related Cheatsheets

Master React with Stanza

Interactive lessons and challenges, right in your code editor.

Check the free courses. No credit card.