A context Provider in the Next.js App Router has to live in a file marked "use client", because React context depends on reading state during a component's render in the browser, and that mechanism does not exist in a Server Component. This applies to createContext, the Provider component it returns, and every place that reads from it with useContext.
The fix is not to make the whole app client-side. You write one small client file that holds the Provider and a matching hook, then render that file from a Server Component, usually the root layout, which never needs the directive itself.
// app/theme-provider.tsx
'use client'
import { createContext, useContext, useState } from 'react'
const ThemeContext = createContext<'light' | 'dark'>('light')
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme] = useState<'light' | 'dark'>('dark')
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
}This file runs on the client, since it holds the context object, the Provider component, and the state that feeds it. The directive at the top is required, not optional, because none of those three pieces work during a server render.
The same file also exports the reader hook, so anything that needs the current theme imports one function instead of reaching into the context object directly.
// app/theme-provider.tsx (continued, same file)
export function useTheme() {
return useContext(ThemeContext)
}Any component that calls this hook must also be a Client Component, for the same reason the provider itself needs the directive.
Why context needs the client
React Server Components render once on the server and produce output, with no ongoing render cycle for React to attach state to. Context works by having a Provider hold a value and letting a reader re-check that value on every render of a subscribed component, which only makes sense in an environment that keeps rendering after the first pass.
createContext(...) -> can be called anywhere, but only matters at render time
Provider component -> requires client rendering
reading the value -> requires client renderingBecause of this, Next.js treats a Provider like any other stateful component: the file that defines it needs "use client", and that boundary applies to the whole module, not just the createContext call. See The use client Directive Explained for how that boundary spreads through a file's imports.
Where to mount the provider
Most providers only need to exist once for the whole app, so the usual place to render one is the root layout, wrapping the children prop.
// app/layout.tsx
import { ThemeProvider } from './theme-provider'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en"><body>
<ThemeProvider>{children}</ThemeProvider>
</body></html>
)
}The root layout file has no client directive of its own, so it stays a Server Component. Importing and rendering the provider does not convert the file that imports it. Next.js renders the layout on the server, and the provider is the point where the tree becomes client-rendered from that branch down.
Notice the provider wraps only the children prop, not the surrounding html and body tags. Keeping the wrapper as deep as possible, right against the content that actually needs the context, leaves more of the surrounding layout markup eligible to render on the server instead of joining the client bundle.
Server Components can still nest inside the provider
Everything rendered as children of the provider still passes through the root layout, and that content does not become client code just because it visually sits inside a Client Component's subtree.
// app/dashboard/page.tsx
import { getMetrics } from '@/lib/data'
export default async function DashboardPage() {
const metrics = await getMetrics()
return <p>Total signups: {metrics.signups}</p>
}This dashboard page fetches data on the server and never imports the theme provider file, so it stays a Server Component even though the root layout renders it inside the provider. This is the same children composition pattern covered in Passing Server Components as Children to Client Components: the provider only receives already-rendered output through children, never the page's source module.
Only a component that actually calls the theme hook needs to become a Client Component itself.
// app/dashboard/theme-toggle.tsx
'use client'
import { useTheme } from '@/app/theme-provider'
export default function ThemeToggle() {
const theme = useTheme()
return <span>Current theme: {theme}</span>
}This component reads the context, so it needs its own client directive, separate from the provider file. It can sit anywhere below the provider in the tree and still receive the current value.
Common mistakes
The two mistakes below both come from misunderstanding what the directive actually marks.
| Mistake | Why it fails |
|---|---|
Adding "use client" to the entire root layout instead of a small provider wrapper | Pulls the whole layout's imports into the client bundle, not just the provider |
| Reading context inside a Server Component | Fails at build time, because context reads only work during client rendering |
Marking the root layout itself as a Client Component is the most common version of the first mistake. It happens when someone defines the context and the Provider directly inside the layout file instead of a separate file. That forces the directive onto the layout.
Once the layout carries the directive, every page it renders joins the client bundle along with it. Moving the provider into its own file, as shown above, fixes this without changing what the app renders. For more on deciding where that directive belongs, see When to Add use client and When Not To.
The second mistake shows up when a Server Component tries to read shared state directly instead of receiving it as a prop.
// This fails: context reads are not available in a Server Component
import { useContext } from 'react'
import { ThemeContext } from './theme-provider'
export default function Header() {
const theme = useContext(ThemeContext)
return <header>{theme}</header>
}If a Server Component needs a value that also happens to live in context, fetch or compute that value on the server and pass it down as a prop instead. The other option is turning the component that needs it into a Client Component with its own hook call. For the full set of composition options between the two component types, see Composition Patterns for Server and Client Components.
Rune AI
Key Insights
- createContext, a Provider, and reading context all require client rendering, so the file that defines them needs use client.
- Mount the provider once, usually as a thin wrapper rendered from the root layout.
- The root layout stays a Server Component. It imports the provider file, but importing a Client Component does not convert the importer.
- Wrap only the children prop in the provider, not the whole html document, so more of the layout can stay server rendered.
- Server Components nested inside the provider tree keep rendering on the server, because they arrive as children, not as an import.
- Reading context only works inside a Client Component. A Server Component cannot do it directly.
Frequently Asked Questions
Does the root layout need use client if it renders a context provider?
Can a Server Component read a context value directly?
Where should the provider wrapper sit in the tree?
Can a Server Component be a child of a context provider without becoming a Client Component?
Conclusion
React context needs a Provider component, and a Provider always needs use client, because context depends on client-side rendering. Keep that directive on one small wrapper file, render it from the root layout without adding use client to the layout itself, and every Server Component nested inside the provider tree keeps rendering on the server through normal composition.A context Provider belongs in its own small file with "use client", mounted once from a Server Component like the root layout, wrapping only the branch of the tree that needs it. The layout that renders the provider does not need the directive itself, and Server Components composed as children inside the provider tree keep rendering on the server the entire time. That combination is what lets shared client state and server-rendered content share one App Router tree without either side absorbing the other.
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.