Web · React · Next.js
React Server Components in Practice: What Changes and What Doesn't
Server Components change where your React code runs, not how you think about UI. A practical guide to the server/client split in the Next.js App Router.
· 2 min read · By george
With the Next.js App Router, every component is a Server Component unless you say otherwise. That one default changes where code runs, what ships to the browser, and how you fetch data, but it doesn't change how you think about UI. You're still composing components.
What Server Components are
A Server Component renders on the server (at build time or on request) and sends the result to the browser. Its own JavaScript never ships to the client. That means it can:
awaitdata directly: query a database, read a file, call an internal API with a secret.- Import heavy libraries (Markdown parsers, syntax highlighters) without adding to the bundle.
// app/blog/page.jsx — a Server Component
export default async function BlogPage() {
const posts = await db.post.findMany({ where: { published: true } });
return <PostList posts={posts} />;
}
No useEffect, no loading state, no API route just to feed your own page.
What they can't do
Server Components don't re-render in the browser, so they can't use state, effects, event handlers or browser APIs. Anything interactive needs a Client Component, marked with "use client" at the top of the file:
"use client";
import { useState } from "react";
export function LikeButton({ initialCount }) {
const [count, setCount] = useState(initialCount);
return <button onClick={() => setCount(count + 1)}>♥ {count}</button>;
}
The rule of thumb: push "use client" down
"use client" marks a boundary: that file and everything it imports becomes client code. Put it as low in the tree as you can. Keep the page, layout and data fetching on the server, and make only the interactive leaf (the button, the form, the carousel) a Client Component.
A Server Component can render a Client Component and pass it props, as long as the props are serialisable (plain data, not functions or class instances). It can also pass Server Components to a Client Component as children:
<ClientTabs>
<ServerRenderedArticle /> {/* still rendered on the server */}
</ClientTabs>
Common mistakes we see
- Marking the whole page
"use client"to make one button work. You lose server data fetching and ship far more JavaScript than needed. - Passing secrets as props. Everything passed to a Client Component ends up in the browser. Keep tokens and keys in server-only code.
- Fetching in
useEffectout of habit. If the data is needed for the first render, fetch it in a Server Component instead. - Forgetting caching. In the App Router you decide per route whether output is static, revalidated on a timer, or rendered per request. Make that choice deliberately.
What doesn't change
Components, props, composition, and good component boundaries all work the same as before. Server Components just add one more question to each component you write: does this need to run in the browser? Most of the time, the answer is no, and your users get a faster page because of it.
Comments
Loading comments…