Back to blog
2 min read

Server Components: Rethinking Where Your Code Runs

  • Next.js
  • React
  • Architecture

For years, a React component was a client thing: it shipped to the browser, ran there, and fetched its data over the network. The Next.js App Router flips the default. A component now runs on the server unless you opt out. It is a small change in syntax and a large change in how you design.

Server by default

Every component under app/ is a server component until you say otherwise. Server components render on the server and send HTML. Their code never reaches the browser bundle. That means a server component can:

  • Read the filesystem or a database directly
  • Use secrets and API keys safely
  • Import heavy libraries without growing the client bundle

What it cannot do is anything interactive: no useState, no useEffect, no event handlers, no browser APIs.

The client boundary

To get interactivity, you mark a file with a directive:

"use client";

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

"use client" marks an entry point into client code. That component and everything it imports ship to the browser. The goal is to keep these boundaries small and push them toward the leaves of the tree.

Data fetching moves into the component

In a server component you fetch data where you use it, with no loading-state plumbing:

export default async function Page() {
  const posts = await getPosts(); // runs on the server
  return <PostList posts={posts} />;
}

The component is async. There is no useEffect, no client-side fetch, no waterfall of requests after the page loads.

Composition across the boundary

The pattern that trips people up: a client component cannot import a server component, but it can receive one as a child.

// inside a client component
<Tabs>
  <ServerChart />
</Tabs>

ServerChart is passed as a prop, so it still renders on the server. Tabs handles the interactivity; the chart stays off the client bundle. This lets an interactive shell wrap server-rendered content without dragging it into the browser.

When to reach for a client component

Use a client component when you need state, effects, event handlers, or browser-only APIs. Use a server component for everything else, which is usually most of the tree: layout, text, data display, navigation.

A useful check: if a component only arranges and displays data, it belongs on the server. If it responds to the user, it needs the client. Drawing that line well is most of what designing for the App Router is about.