Architecture note
Choosing the right execution boundary
Frontend Architecture
Next.js Server Components Explained: When to Use Them
Updated November 4, 2026
Why this boundary matters
A component boundary determines where code runs, what data can be accessed directly, and which JavaScript must be sent to the browser. A Server Component can read a database or call a private service without shipping those implementation details to the client. A Client Component can respond to clicks and keystrokes, but its code becomes part of the browser bundle.
This distinction is especially useful for content-heavy pages. An article can be rendered as server output while a small table of contents, search box, or copy button remains interactive. Visitors receive useful content without making the entire article page a client-side application.
Server Components in a real page
The following example keeps the page and data access on the server. The result is a normal component tree; there is no special loading state in the page itself because the framework can stream or render the server result according to the route configuration.
// app/projects/[slug]/page.tsx
import { notFound } from 'next/navigation';
export default async function ProjectPage({ params }) {
const project = await getProject(params.slug);
if (!project) notFound();
return (
<main>
<h1>{project.name}</h1>
<ProjectSummary project={project} />
<ProjectFilter options={project.filters} />
</main>
);
}The page can fetch the project because it runs on the server. The filter can still be interactive, but it should be isolated rather than forcing the entire route to become a Client Component.
When to use a Client Component
Add "use client" when the component uses React state, event handlers, effects, browser APIs such as the Clipboard API, or a browser-only package. Examples include a live form, a modal, a drag-and-drop editor, a theme switcher, and a tool that updates its output while a visitor types.
// components/ProjectFilter.tsx
'use client';
import { useState } from 'react';
export function ProjectFilter({ projects }) {
const [query, setQuery] = useState('');
const visible = projects.filter((project) =>
project.name.toLowerCase().includes(query.toLowerCase())
);
return (
<>
<input value={query} onChange={(event) => setQuery(event.target.value)} />
{visible.map((project) => <p key={project.id}>{project.name}</p>)}
</>
);
}Notice that the interactive part owns the state. If the project data is loaded by the parent, pass only the serializable fields that the filter needs. This keeps the boundary understandable and avoids sending unrelated server code to the browser.
A decision checklist
- Start with a Server Component.
- List the exact behavior that requires a browser.
- Move only that behavior into a Client Component.
- Pass serializable values across the boundary.
- Test the route with JavaScript disabled to verify its core content remains useful.
- Measure browser JavaScript and interaction performance after the change.
Data fetching, caching, and errors
Server Components do not automatically make every data request correct or inexpensive. Decide how fresh the data needs to be, configure caching deliberately, and expose an error state when an upstream request fails. A page that silently renders stale or empty content is not improved simply because it rendered on the server.
For an article site, static or revalidated content is often a good fit. For a live dashboard, you may need a combination of server-rendered initial data and client-side refresh. The right choice depends on freshness, cost, and interaction requirements—not on a blanket rule that one rendering mode is always best.
Common mistakes
- Adding
"use client"to a whole route because one button is interactive. - Passing functions, database clients, or class instances across the boundary.
- Putting secrets in code that is imported by a Client Component.
- Assuming server rendering replaces semantic HTML, accessibility, or performance testing.
- Copying a caching configuration without checking the freshness requirement.
How this applies to a content site
This site uses server-rendered article pages and keeps browser interaction inside tools such as the JSON Formatter and LLM Token Estimator. That separation makes the articles crawlable and keeps the interactive code close to the features that need it. It also makes the privacy behavior easier to explain: the tools process input locally rather than sending it to an application API.
FAQ
Do Server Components eliminate JavaScript?
No. They reduce client JavaScript for server-rendered parts. Interactive components still require JavaScript, and a production page should be tested with its intended interactions enabled.
Are Server Components automatically better for SEO?
Rendering alone does not make content helpful or authoritative. Server output can make important content and metadata available earlier, but quality, usefulness, crawlable links, accessibility, and page experience still matter.
Should every component be a Server Component?
Use the server by default, then choose a client boundary based on behavior. Forms, filters, editors, and browser-only APIs generally belong in Client Components.