Context in Server Components — Patterns for Shared State
In this tutorial, you will learn about Context in Server Components. We cover key concepts, practical examples, and best practices to help you master this topic.
React Context is a client-side feature. Server Components cannot use context directly, but patterns exist to share data between Server Components and Client Components.
What You'll Learn
You will understand why Server Components cannot use context, how to provide context from Client Components, and patterns for sharing data across the boundary.
Why It Matters
Context is essential for themes, authentication state, localization, and global UI state. Understanding how to bridge context across the server-client boundary is critical for real applications.
Real-World Use
DodaTech's tutorial platform uses a Client Component ThemeProvider that wraps the entire app, while Server Components consume theme-related CSS variables without needing the context directly.
flowchart TD
A[Root Layout] --> B[Client ThemeProvider]
B --> C[Server Page Component]
C --> D[Client AuthProvider]
D --> E[Server Content]
E --> F[Client Interactive Elements]
B -->|Theme Context| F
D -->|User Context| F
style B fill:#0f172a,color:#fff
style D fill:#0f172a,color:#fff
style C fill:#1e293b,color:#fff
style E fill:#1e293b,color:#fff
Why Server Components Cannot Use Context
Context providers and consumers rely on React's client-side fiber tree. Server Components render once and do not maintain a component tree in memory.
// This will NOT work
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
async function ServerComponent() {
const theme = useContext(ThemeContext); // ERROR
return <p>Current theme: {theme}</p>;
}
Expected output: A build error because Server Components cannot use hooks including useContext. Context requires client-side runtime.
Wrapping with Client Component Providers
Create a Client Component provider and wrap your Server Components inside it.
'use client';
import { createContext, useContext, useState } from 'react';
const AuthContext = createContext(null);
export function AuthProvider({ children, initialUser }) {
const [user, setUser] = useState(initialUser);
return (
<AuthContext.Provider value={{ user, setUser }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}
Usage in layout:
// app/layout.js — Server Component
import { AuthProvider } from '@/components/AuthProvider';
import { cookies } from 'next/headers';
export default async function RootLayout({ children }) {
const session = await getSession(cookies());
return (
<html>
<body>
<AuthProvider initialUser={session?.user || null}>
{children}
</AuthProvider>
</body>
</html>
);
}
Expected output: The AuthProvider Client Component wraps the entire app. Server Components render inside it. Client Components deeper in the tree can use the useAuth hook to access user data.
Passing Server Data to Context Providers
Server Components fetch initial data and pass it to Client Component context providers.
'use client';
import { createContext, useContext, useState } from 'react';
const CartContext = createContext({ items: [], addItem: () => {} });
export function CartProvider({ children, initialItems }) {
const [items, setItems] = useState(initialItems);
const addItem = (product) => setItems(prev => [...prev, product]);
return (
<CartContext.Provider value={{ items, addItem }}>
{children}
</CartContext.Provider>
);
}
export function useCart() {
return useContext(CartContext);
}
// Server Component
import { CartProvider } from '@/components/CartProvider';
export default async function ShopLayout({ children }) {
const cartItems = await db.cart.findBySession(sessionId);
return (
<CartProvider initialItems={cartItems}>
{children}
</CartProvider>
);
}
Expected output: The server fetches the current cart items and passes them as initial data to the CartProvider. The Client Component manages cart state after the initial load.
Consuming Context in Server-Adjacent Components
Client Components that consume context can be placed inside Server Components.
'use client';
import { useAuth } from './AuthProvider';
function UserMenu() {
const { user, setUser } = useAuth();
return (
<div>
{user ? (
<span>Welcome, {user.name}</span>
) : (
<button onClick={() => setUser({ name: 'Guest' })}>Login</button>
)}
</div>
);
}
// Server Component uses it
export default async function Header() {
return (
<header>
<nav>Site Navigation</nav>
<UserMenu />
</header>
);
}
Expected output: The Header Server Component renders navigation as static HTML and embeds the UserMenu Client Component. UserMenu accesses AuthContext provided higher in the tree.
Context Composition with Multiple Providers
Combine multiple providers in a single Client Component wrapper.
'use client';
import { AuthProvider } from './AuthProvider';
import { CartProvider } from './CartProvider';
import { ThemeProvider } from './ThemeProvider';
export function AppProviders({ children, initialAuth, initialCart }) {
return (
<ThemeProvider>
<AuthProvider initialUser={initialAuth}>
<CartProvider initialItems={initialCart}>
{children}
</CartProvider>
</AuthProvider>
</ThemeProvider>
);
}
Usage:
export default async function RootLayout({ children }) {
const [session, cartItems] = await Promise.all([
getSession(),
db.cart.findBySession(sessionId),
]);
return (
<html>
<body>
<AppProviders initialAuth={session?.user} initialCart={cartItems}>
{children}
</AppProviders>
</body>
</html>
);
}
Expected output: A single AppProviders Client Component wraps all context providers. The server fetches initial data for each provider and passes it as props.
Common Mistakes
Trying to use useContext in Server Components: Server Components cannot use any hooks. Move context consumption to Client Components.
Not providing initial data to context: If context starts with null or empty state, Client Components might render incorrectly before data loads. Pass server-fetched initial data.
Creating context providers as Server Components: Context providers must be Client Components because they manage state. Wrap them with
'use client'.Putting providers too deep in the tree: Providers should wrap the section that needs the context. Putting them too high or too low affects which components can consume them.
Not memoizing context values: If the context value changes on every render, all consumers re-render. Use useMemo for complex context values.
Practice Questions
- Why can't Server Components use React Context?
Context relies on React's client-side component tree and hooks. Server Components render once and do not maintain a mutable tree.
- How do you provide context to Server Components?
Wrap Server Components inside a Client Component provider. The provider is a Client Component that manages state and passes it to children.
- Can a Server Component pass data to a context provider?
Yes. The Server Component fetches initial data and passes it as props to the Client Component provider, which uses it as the initial state.
- How do multiple context providers work with Server Components?
Nest the providers in a single Client Component wrapper. The Server Component renders the wrapper, which contains all context providers.
- Can Server Components consume context values indirectly?
Not directly. But they can receive data from Client Components through Server Actions or through the children pattern.
Challenge
Create an authentication system where a Server Component reads session cookies and passes user data to a Client Component AuthProvider. Build a protected dashboard page that uses the auth context.
Frequently Asked Questions
Mini Project
Build a multi-tenant dashboard where a Server Component fetches the tenant configuration and passes it to a ThemeProvider and FeatureFlagProvider. Client Components consume both contexts for themed, feature-gated rendering.
What's Next
Learn about Client Bundles to minimize the JavaScript sent to the browser.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro