Next.js Server vs Client Components: when to use each + 6 errors
Published: September 7, 2026 · 7 min read
When to use Server vs Client Components in the Next.js App Router, where "use client" belongs, 6 beginner errors and fixes, props, children, and app file roles.
Server vs Client Components: where "use client" goes
You start a project on the Next.js App Router, add useState to a button, and it errors.
You add "use client" (a marker that says this component runs in the browser) because the message says so, it works, and you still do not know where that line belongs. Almost everyone trips here first.
The answer is simple.
Write components with nothing extra so they are Server Components, and add the directive only to files that need state, events, or browser APIs. Put it on the smallest leaf components you can.
This guide covers the following, with code.
How Server and Client Components differ, and why you split them
How to tell when the directive is needed
Six errors beginners hit, and how to fix each
props and children, and what the app folder filenames do
Server Components vs Client Components in Next.js
A component is a function that owns a piece of UI. In the App Router, anything you do not mark is a Server Component. It runs on the server, and the finished HTML reaches the browser.
A Client Component is a file with "use client" on the first line. Its initial HTML is still rendered on the server, but JavaScript then runs in the browser too, so it can respond to clicks and input.
Three things Server Components give you
Less JavaScript shipped to the browser, so pages are lighter
Data fetching from a database or API written directly in the component
Secrets such as API keys used without leaking to the browser
Make everything a Client Component and you give up all three. That is why the usual advice is server by default, client only where needed.
When a Client Component ("use client") is needed
A file needs the directive if it uses any of the following. If it uses none, write nothing.
React hooks such as useState, useEffect, useRef
Events such as onClick and onChange
Browser APIs such as window and localStorage
Libraries that assume the browser, such as Motion (formerly Framer Motion)
Put it on the first line of the file, before any import.
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}Rules for mixing Server and Client Components
A Server Component can import a Client Component. Anything a Client Component file imports also runs on the client.
Components passed in as children or props are the exception. Pass a Server Component as children to a client wrapper and it still renders on the server. Error 3 below relies on this.
6 Server and Client Component errors and fixes
Nearly every server/client error is one of these six. Exact wording shifts between Next.js versions, so match on meaning.
Error 1: useState or useEffect is not allowed
The message says the hook only works in a Client Component. Add the directive to the top of that file. If it happens in page.tsx, move the hook into its own file first and mark that file instead.
Error 2: a click handler cannot be passed down from the parent
Create a function in a Server Component and pass it as a prop to a client component, and you get an error saying event handlers cannot be passed to Client Component props. A regular function created on the server cannot be sent to the browser (functions marked "use server" are the exception).
Move the click logic inside the Client Component. From the parent, pass plain values such as strings and numbers only.
"use client";
import { useState } from "react";
// Logic lives inside the client component; the parent passes values only
export function LikeButton({ initial }: { initial: number }) {
const [likes, setLikes] = useState(initial);
return <button onClick={() => setLikes(likes + 1)}>♥ {likes}</button>;
}Error 3: Context does not work in layout.tsx
Using createContext in layout.tsx to share a theme or auth state errors, because Context is a client feature.
Move just the Provider into a Client Component and wrap children with it from layout.tsx. Pages passed as children stay Server Components.
"use client";
import { createContext, useState, type ReactNode } from "react";
export const ThemeContext = createContext<"light" | "dark">("light");
export function Providers({ children }: { children: ReactNode }) {
const [theme] = useState<"light" | "dark">("light");
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}
// in app/layout.tsx
// <body><Providers>{children}</Providers></body>Error 4: an environment variable can't be read in the browser
Reading process.env.API_KEY in a Client Component gives you no value (undefined or an empty string). Only variables whose names start with NEXT_PUBLIC_ reach the browser.
Move secret-key work to a Server Component or a Route Handler (an API under app/api/). Adding NEXT_PUBLIC_ to make it work exposes the key to everyone.
Error 5: server and browser render different output
This is a hydration error (hydration is attaching browser JavaScript to server-rendered HTML). Using new Date(), Math.random(), or window during render produces different results on server and browser.
Read browser-only values inside useEffect and store them in state. The first render then matches the server, so nothing mismatches.
"use client";
import { useEffect, useState } from "react";
export function Clock() {
const [now, setNow] = useState<string | null>(null);
useEffect(() => {
setNow(new Date().toLocaleTimeString());
}, []);
return <p>{now ?? "--:--:--"}</p>;
}Error 6: "use client" placed too high makes the page heavy
No error message appears, yet this is the most common mistake. Put the directive on page.tsx or layout.tsx to silence Error 1, and everything imported from there runs on the client. Server-side data fetching is gone too.
The same applies when animating a hero with Motion. Mark only the moving component's file and import it from the page.
"use client";
import { motion } from "motion/react";
import type { ReactNode } from "react";
export function FadeIn({ children }: { children: ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
{children}
</motion.div>
);
}
// app/page.tsx (still a Server Component)
// <FadeIn><h2>Features</h2></FadeIn>props and children in Server Components
There are two ways to hand data to a component: props and children. Both work in Server and Client Components.
props: values that change per page
Values that change per page, like a header title, come in as props. Props are read-only; the receiving component must not mutate them.
type SiteHeaderProps = { title: string };
export function SiteHeader({ title }: SiteHeaderProps) {
return (
<header className="border-b px-4 py-3">
<h1 className="text-lg font-bold">{title}</h1>
</header>
);
}
// usage
// <SiteHeader title="DesignLayer" />children: whatever sits between the tags
For a card with a shared frame and varying content, use children. Whatever you write between the tags is passed through as the content.
import type { ReactNode } from "react";
export function Card({ children }: { children: ReactNode }) {
return <div className="rounded-lg border p-4 shadow-sm">{children}</div>;
}
// usage: inner content is free
// <Card>
// <h2>Pricing</h2>
// <p>From $10 / month</p>
// </Card>What filenames decide, and Client Components
In the App Router, reserved filenames have jobs. The ones you will use most:
page.tsx: the screen for that URL
layout.tsx: chrome shared by child pages; not recreated on navigation
loading.tsx: shown automatically while data loads; where skeletons live
error.tsx: the error screen; the directive is required
not-found.tsx: the 404 screen
template.tsx: like layout, but recreated on every navigation; use it when an entrance animation must replay
The one people forget is the directive on error.tsx. error.tsx works as a React error boundary, which runs on the client, so it must be a Client Component even without a retry button.
"use client";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>Try again</button>
</div>
);
}Split Server and Client Components by job
Put fetching, rendering, and click handling in one component and later edits get painful. Split by job and the server/client boundary falls into place.
Fetch: an async Server Component such as page.tsx
Display: components that only take props and render; no directive needed
Interaction: only the small pieces that handle clicks or input become Client Components
import { LikeButton } from "@/components/LikeButton";
type Post = { id: string; title: string; likes: number };
// Fetch: Server Component
export default async function PostsPage() {
const res = await fetch("https://example.com/api/posts");
const posts: Post[] = await res.json();
return <PostList posts={posts} />;
}
// Display: props only
function PostList({ posts }: { posts: Post[] }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
{post.title}
{/* Interaction: only this part is client */}
<LikeButton initial={post.likes} />
</li>
))}
</ul>
);
}Move animated parts into Client Components
You do not need to write generic UI like buttons and forms from zero each time. shadcn/ui, the common choice, copies component source into your repo, so you can change it freely later.
npx shadcn@latest add buttonUsing animated Client Component sections
Entrance and scroll-linked sections can take half a day each to write.
On DesignLayer you run motion React sections in the browser and copy the TSX + Tailwind CSS source. The directive and the package list come with it, so you will not end up marking the whole page as in Error 6. https://design-layer.com/en/components
If you write Motion yourself, start with https://design-layer.com/en/articles/nextjs-framer-motion-guide
Server and Client Components: common questions
Its initial HTML is rendered on the server. JavaScript then also runs in the browser so it can respond to interaction. Think of "use client" as a marker meaning "this also runs in the browser."
Summary: Server vs Client Components in Next.js
Write components as Server Components by default; add "use client" only to files needing state, events, or browser APIs
Put that line on small leaf components, not on the whole page or shared layout
Do not pass event handlers from a Server Component; use secrets on the server; read browser-only values in useEffect
Passed as children, Server Components still render on the server inside a client wrapper
In the app folder, the filename decides the job
Once the basics click, building a real landing page is the fastest way to learn. The one-day Cursor workflow is in https://design-layer.com/en/articles/cursor-nextjs-landing-page-guide
To add motion to a hero, see https://design-layer.com/en/articles/nextjs-animated-hero-section-guide


