React hooks in Server Components mostly do not work. A Server Component cannot use useState, useEffect, useReducer, or useContext, because these hooks only work in Client Components, and they depend on a component instance that stays alive in the browser across renders.
A Server Component renders once on the server to produce output, then it is done. There is nothing left to hold state, run an effect after a re-render, or subscribe to a context update, so React refuses to let these hooks run there at all.
// app/dashboard/page.tsx
import { useState } from 'react'
export default function DashboardPage() {
const [open, setOpen] = useState(false)
return <button onClick={() => setOpen(!open)}>{open ? 'Close' : 'Open'}</button>
}This page has no "use client" directive, so it is a Server Component by default. Calling the hook inside it fails the build instead of quietly doing nothing, because Next.js detects it while tracing the module graph on the server before any code ever reaches the browser.
Why this fails
Client-side hooks are built around React's render and commit cycle in the browser. State needs a stored value that survives between renders, an effect needs a commit phase to run after, and context needs a live subscription to a Provider that can push new values later.
A Server Component skips all of that. It runs once during the server render, streams its output, and the function itself never runs again for that request, so there is no later render for a stored value or an effect to attach to. React Server Components exist to produce output once and hand it off, not to sit in memory waiting for a future update. That single-pass model is exactly what makes them cheap to run and safe to hold server-only secrets, but it also means none of the hooks built around repeated renders have anything to attach to.
The visible result
Running the dashboard page above produces a build-time error from Next.js, not a silent failure or a blank button. The build stops before the app ever serves that route.
Error: useState only works in Client Components. Add the "use client" directive at the top of the file to use it.
Read more: https://nextjs.org/docs/messages/react-client-hook-in-server-componentThe message names the exact hook and points to the fix directly. This is a build-time check, so the app will not start or deploy until the file is corrected, which is safer than shipping a button that silently does nothing when a real visitor clicks it.
The fix
Add "use client" to the top of the file that actually calls the hook, above every import.
// app/dashboard/page.tsx
'use client'
import { useState } from 'react'
export default function DashboardPage() {
const [open, setOpen] = useState(false)
return <button onClick={() => setOpen(!open)}>{open ? 'Close' : 'Open'}</button>
}The directive is needed here because the component now holds state and responds to a click, and both require a Client Component. This file still renders once on the server to produce the initial HTML, then React hydrates it in the browser so the click handler becomes active. Runs on both the server, for that first pass, and the browser, for every render after that.
Which hooks need a Client Component
Every hook that depends on state, an effect, a ref to a DOM node, or a live subscription needs a Client Component. This covers the hooks developers reach for most often.
| Hook | Works in a Server Component |
|---|---|
| useState, useReducer | No |
| useEffect, useLayoutEffect | No |
| useContext (consuming a Provider) | No |
| useRef (DOM refs), useTransition, useOptimistic | No |
| useActionState | No |
useActionState is easy to misjudge because it wraps a Server Action, but the hook itself still tracks pending and returned state across renders in the browser, so the component calling it needs a client directive even though the action it calls runs on the server.
The narrow exceptions that work on the server
A Server Component can still be an async function and await data directly in its body, without any hook at all. This is a React feature built for Server Components specifically, separate from the rules that govern hooks.
// app/dashboard/page.tsx
import { getMetrics } from '@/lib/data'
export default async function DashboardPage() {
const metrics = await getMetrics()
return <p>Active users: {metrics.activeUsers}</p>
}This entire file runs on the server only. The await call reads data during the render itself, so no client-side hook is involved and nothing here ships to the browser.
React's cache() function is the other exception. It is built to run in Server Components, and it dedupes repeated calls to the same function during a single server render so several components can request the same data without triggering duplicate work.
// lib/data.ts
import { cache } from 'react'
export const getMetrics = cache(async (teamId: string) => {
const res = await fetch(`https://api.example.com/teams/${teamId}/metrics`)
return res.json()
})Two different Server Components can both call getMetrics with the same team id during the same render, and the underlying fetch only runs once. This module has no directive and never runs in the browser, since cache() itself is documented for use in Server Components only.
Common confusion
Developers often assume that because a Server Component can render a context Provider, it can also read a value back out of that same context. Rendering a Provider and consuming its value are different operations, and only reading a value with useContext is a hook that requires a Client Component.
// app/theme-provider.tsx
'use client'
import { createContext } from 'react'
export const ThemeContext = createContext('light')
export default function ThemeProvider({ children }: { children: React.ReactNode }) {
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
}A Server Component can import and render the theme provider around its children with no error, because it is only placing a Client Component in the tree. Any component that needs to read the theme value back out still has to be a Client Component of its own, since the read itself is what needs the hook.
For the broader split between the two component types, see Server Components vs Client Components in Next.js. For the mechanics of the directive itself, see The use client Directive Explained. For the difference between Next.js caching and React's own dedupe function, see Next.js Request Deduplication vs React cache: How They Differ, and for guidance on deciding where the boundary should sit, see When to Add use client and When Not To.
Rune AI
Key Insights
- Server Components cannot use useState, useEffect, useReducer, useContext, useRef for DOM refs, useTransition, useOptimistic, or useActionState.
- These hooks need a persistent component instance that re-renders in the browser, which a Server Component never has.
- Next.js reports this at build time as a React client hook in Server Component error, naming the hook and telling you to add use client.
- A Server Component can still be an async function and await data directly, which is not a hook.
- React's cache() function can run in a Server Component to dedupe repeated data requests during one render.
- A Server Component can render a context Provider if that Provider is itself a Client Component.
- Fix the error by adding use client to the file where the hook is actually called, not to every parent above it.
Frequently Asked Questions
Can a Server Component use useContext to read a value from a Provider?
Is async/await in a Server Component a hook?
Does useActionState work in a Server Component?
Why does my error mention useState when I never imported it directly?
Conclusion
Server Components cannot use hooks that depend on state, effects, or a persistent render lifecycle, because a Server Component renders once on the server and never re-renders in place. Reach for a Client Component whenever a piece of UI needs useState, useEffect, useContext, or any hook built on top of them, and keep everything else, including async data fetching, on the server by default.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.