React Server Components (RSCs) are one of the most exciting advancements in modern React. They make your app faster and more efficient by letting the server handle rendering tasks that don’t need to happen in the browser. However, one of the biggest points of confusion for developers adopting RSCs is why React Context doesn’t work as expected in Server Components.
Let’s unpack this in detail — why React Context can’t be used in Server Components, what actually happens under the hood, and the correct pattern you should use instead.
The Core Problem: Context Lives on the Client
React Context was designed for runtime state sharing in the browser. It allows you to create a global-like state that React components can access without prop drilling.
But Server Components are rendered on the server, not in the browser. When the server renders your component tree, there is no persistent runtime — no React tree in memory that can share state between requests.
In other words:
React Context depends on a live React tree, but Server Components are stateless and ephemeral — they render, serialize output, and disappear.
That’s why you can’t use hooks like useContext (or any stateful hook) in Server Components. These hooks assume a persistent React runtime that only exists in the client.
What Happens If You Try to Use Context in a Server Component?
If you attempt to do something like this:
// context/theme-context.js
import { createContext, useContext } from 'react';
export const ThemeContext = createContext('light');
export const useTheme = () => useContext(ThemeContext);
And then use it in a Server Component:
// app/page.js (Server Component)
import { useTheme } from '../context/theme-context';
export default function Page() {
const theme = useTheme(); // This will throw an error
return <div>{theme}</div>;
}
You’ll get an error like:
“ReactContext can only be used in Client Components.”
That’s React’s way of telling you: Context doesn’t exist during server rendering in RSC mode.
Why Context Works in Client Components
Client Components run in the browser, so they have a live React runtime. When you wrap part of your app in a provider:
<ThemeContext.Provider value="dark">
<Header />
<Footer />
</ThemeContext.Provider>
React can track that context state across renders because it’s operating within a single, persistent client-side tree.
Server Components, on the other hand, are executed per request — the tree is destroyed after each render. There’s no shared in-memory state.
The Correct Pattern Instead: Pass Data via Props
Instead of relying on React Context, the recommended pattern in Server Components is to pass data down explicitly through props.
For example, if you want to share theme information:
// app/layout.js (Server Component)
import Page from './page';
export default async function Layout() {
const theme = 'dark'; // Could also be fetched from DB or cookies
return <Page theme={theme} />;
}
And then:
// app/page.js (Server Component)
export default function Page({ theme }) {
return <div className={theme}>Hello from {theme} mode!</div>;
}
This works perfectly with RSC because props are serialized between server and client boundaries.
When You Actually Need Context
Sometimes, you truly need shared client-side state — like managing a theme toggle or authentication status after hydration. In those cases, the pattern is:
- Wrap your app with a Client Component Provider.
- Use Context only inside Client Components.
Example:
// components/theme-provider.jsx (Client Component)
'use client';
import { createContext, useState, useContext } from 'react';
const ThemeContext = createContext();
export const useTheme = () => useContext(ThemeContext);
export function ThemeProvider({ children, initialTheme }) {
const [theme, setTheme] = useState(initialTheme);
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
Then use it in your layout:
// app/layout.js (Server Component)
import { ThemeProvider } from '../components/theme-provider';
export default function Layout({ children }) {
const theme = 'dark'; // from server or cookies
return (
<html>
<body>
<ThemeProvider initialTheme={theme}>{children}</ThemeProvider>
</body>
</html>
);
}
Now, your server determines the initial state (e.g., dark mode), and your client manages it interactively.
Summary
| Concept | Server Component | Client Component |
|---|---|---|
| React Context | Not supported | Fully supported |
| React State (useState, useContext) | Not supported | Supported |
| Props Passing | Recommended | Works |
| Interactivity (clicks, input, etc.) | Not supported | Supported |
Correct Pattern
- Fetch or compute data on the server.
- Pass that data to Client Components via props.
- Manage any interactive or shared state with Context in Client Components.
Final Thoughts
React Server Components fundamentally shift how we think about state and rendering. The server’s job is to compute and deliver data efficiently — not to manage shared runtime state. Context remains a client-side tool, while Server Components shine in fetching and preparing data for them.
Once you embrace this separation — data on the server, interactivity on the client — you’