Context Providers in the App Router: Where to Put Them

React context needs client rendering, so a Provider must live in its own use client file. Learn where to mount it and how the root layout stays a Server Component around it.

7 min read

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.tsxApp.tsx
// 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.tsxApp.tsx
// 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.

texttext
createContext(...)   -> can be called anywhere, but only matters at render time
Provider component     -> requires client rendering
reading the value       -> requires client rendering

Because 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.tsxApp.tsx
// 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.tsxApp.tsx
// 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.tsxApp.tsx
// 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.

MistakeWhy it fails
Adding "use client" to the entire root layout instead of a small provider wrapperPulls the whole layout's imports into the client bundle, not just the provider
Reading context inside a Server ComponentFails 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.

App.tsxApp.tsx
// 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Does the root layout need use client if it renders a context provider?

No. The root layout imports the provider component and renders it, but importing a Client Component does not convert the importing file into one. The layout stays a Server Component as long as it has no directive of its own.

Can a Server Component read a context value directly?

No. Reading context only works inside a Client Component, because context relies on React state that only exists once a component has mounted in the browser. Pass the value down as a prop from a Server Component instead.

Where should the provider wrapper sit in the tree?

As deep as it can go while still wrapping every component that reads the context. Wrapping only the children prop instead of the whole html document keeps more of the surrounding layout eligible for server rendering.

Can a Server Component be a child of a context provider without becoming a Client Component?

Yes. The provider only receives its children as already-rendered output, not as an import, so a Server Component passed in that way keeps running on the server.

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.