Next.js

use client vs use server in Next.js: The Real Difference

The #1 misconception in the App Router: "use server" is not the opposite of "use client". One marks a client boundary in the module graph; the other exposes callable server functions. Server Components are the default — no directive needed.

Mahdi Ben RhoumaMahdi Ben Rhouma
Updated
8 min read

If you think "use client" and "use server" are opposites — one for client components, one for server components — you have the single most common App Router misconception. They are not symmetric. This guide nails down what each actually does, straight from the React and Next.js docs.

Server Components are the default#

In the App Router, "by default, layouts and pages are Server Components." An unmarked file in app/ is already a Server Component — there is no directive to "make" one. Keep this in mind; it's why "use server" can't be the way to create one.

What "use client" does#

"use client" marks the boundary in the module dependency tree where code switches from server to client. From the React docs: it marks "the module and its transitive dependencies as client code" and defines the boundary "on the module dependency tree, not the render tree."

tsx
// app/ui/counter.tsx
'use client'
import { useState } from 'react'
 
export default function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

The crucial nuance most people miss: the boundary follows imports, not props. A Server Component passed as children to a Client Component stays a Server Component — it's rendered on the server and passed in as output. Only imported descendants get pulled client-side.

The directive must be at the very top of the file, above imports (comments are fine), with single or double quotes — not backticks.

What "use server" does (the misconception)#

"use server" marks Server Functions (a.k.a. Server Actions) — async functions that run on the server but are callable from client code. The React docs: it "marks server-side functions that can be called from client-side code." It does not make a component a Server Component.

tsx
// app/actions.ts
'use server'
import { db } from '@/lib/db'
 
export async function archiveInactiveUsers() {
  // runs on the server, callable from the client
  await db.user.updateMany({ where: { active: false }, data: { archived: true } })
}
tsx
// app/components/archive-button.tsx
'use client'
import { useTransition } from 'react'
import { archiveInactiveUsers } from '../actions'
 
export default function ArchiveButton() {
  const [isPending, startTransition] = useTransition()
  return (
    <button
      disabled={isPending}
      onClick={() => startTransition(() => archiveInactiveUsers())}
    >
      Archive inactive users
    </button>
  )
}

The example is a mutation, not a read, on purpose. The React docs state that Server Functions "are designed for mutations that update server-side state; they are not recommended for data fetching", and that outside a <form action> they "should be called in a Transition". To load data, fetch it in a Server Component and pass it down as props.

It has two legal placements: at the top of a file (marks all exports as Server Functions) or inline at the top of an async function body (marks just that function). It cannot go on a non-async function.

The contrast, side by side#

"use client""use server"
GoesTop of a file, above importsTop of a file, or inline in an async function
MarksA boundary: this module + imports become client codeServer Functions callable from the client
Acts onThe module import graphIndividual server functions
Common mistakePlaced too high → whole subtree ships to clientUsed hoping to "force a Server Component"

The two classic mistakes#

Don't
'use server' at the top of a component file, expecting it to become a Server Component.
Do
No directive at all — components in app/ are Server Components by default. Use 'use server' only for callable server functions.
Don't
'use client' on a layout so one small widget can use useState — now the entire subtree is in the client bundle.
Do
'use client' on the leaf <Counter/> only; keep the layout a Server Component and pass server content via children.

The Next.js docs spell out the second fix: "add 'use client' at the top of the files that define your interactive components instead of marking large parts of your UI as Client Components."

Terminology drift

"Server Actions" was the original name; React/Next now use "Server Functions" as the umbrella term, with "Server Actions" meaning ones wired to forms / action props. If you're searching docs, both terms point at the same "use server" feature.

When this won't work
  • This is App Router only — the Pages Router has neither directive nor Server Components.
  • Some bundlers strip "use client" when building component libraries; library authors need bundler config to preserve it.
  • Both directives are an RSC-era (React 19) feature — they don't apply to a classic React 18 SPA.

Official references: React use client, React use server, Next.js Server and Client Components.

What can actually cross the boundary as a prop#

The article above explains that a Server Component passed as children stays server- rendered. The nuance it doesn't cover: when a Server Component passes a regular prop (not children) down into a "use client" component, that prop has to survive being serialized across the server/client boundary, and not everything can.

Plain objects, arrays, strings, numbers, and booleans cross fine. Functions generally don't — a function defined in a Server Component has no way to run in the browser, so passing one as a prop to a Client Component throws at build or render time, not silently. The one deliberate exception is a Server Function created with "use server": those are serialized as a special reference the client can call back into, which is exactly the mechanism the "use server" half of this article's examples relies on. Plain event handlers or arbitrary closures don't get that treatment — only functions actually marked as Server Functions do.

tsx
// app/page.tsx — Server Component
import ClientButton from './client-button'
 
export default function Page() {
  const onClick = () => console.log('this never runs in the browser')
  // ❌ passing a plain function this way fails — it isn't a Server Function
  return <ClientButton onClick={onClick} />
}

This is the same failure whether the function is inline, imported, or a method on an object you're passing down — if it isn't behind "use server", it isn't callable on the client. Event handlers cannot be passed to Client Component props — fix walks through the exact error message this produces and the two real fixes: move the handler into the Client Component itself, or turn it into a proper Server Function.

Context providers have to be Client Components — which forces a file split#

The Next.js docs are blunt about it: "React context is not supported in Server Components." So any <ThemeProvider>, <AuthProvider>, or other context provider in an App Router project has to be a "use client" component.

The trap this article's examples don't show: putting "use client" directly on the file that also defines your root layout content pulls that entire file's imports into the client bundle — the same "boundary follows imports, not just the one component you meant to mark" problem covered above, just with a provider as the culprit this time instead of a <Counter />.

The fix is a dedicated wrapper file that does nothing except hold the directive and the provider:

tsx
// app/providers.tsx
'use client'
import { createContext } from 'react'
 
export const ThemeContext = createContext('light')
 
export default function Providers({ children }: { children: React.ReactNode }) {
  return <ThemeContext.Provider value="light">{children}</ThemeContext.Provider>
}
tsx
// app/layout.tsx — stays a Server Component
import Providers from './providers'
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

RootLayout itself never gets the directive, so everything it imports besides Providers — and everything passed to it as children — stays server-rendered exactly as described in the deep dive on React Server Components: only the provider file crosses the boundary, not the whole tree it wraps.

Further reading: useSearchParams Suspense Error: 3 Real Fixes (Next.js 15). Also see Fix Unsupported Server Component type: undefined. Also see Unrecognized feature: 'interest-cohort' on GitHub Pages.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.