Skip to main content

How does Context work in Server Components?

How this works

  1. A Provider can be declared on the server You can create a context and set value in a Server Component - every nested Client component will be able to read it via useContext.
  2. The context value must be serializable Everything that goes from the server to the client must be safely serialized. => Use primitives and simple objects/arrays. Not allowed: functions, class instances, proxies, DOM nodes, etc. Allowed: strings, numbers, booleans, null, plain objects, arrays (and anything you're ready to turn into JSON yourself).
  3. Context flows top-down, not back up Context set on the server is available to the client. But state changed in a client Provider cannot "raise" changes back to the server: server components have already been rendered for the request.
  4. A client-only Provider cannot be imported from the server If a Provider uses useState/useEffect hooks and is marked "use client", you cannot import it directly from a Server Component. Wrap the tree through a client "wrapper" component.
  5. Per-request isolation Server Components render on every request. Context created on the server (for example, from cookies/headers/DB) is automatically scoped to a specific request (great for locale, auth, feature flags).

Mini-example (Next.js / RSC)

shared/context.ts

javascript
import { createContext } from "react"; export type AppCtx = { locale: string; userName?: string | null }; export const AppContext = createContext<AppCtx>({ locale: "en" });

app/layout.tsx (Server Component)

javascript
import { cookies } from "next/headers"; import { AppContext } from "@/shared/context"; export default async function RootLayout({ children }: { children: React.ReactNode }) { const cookieStore = await cookies(); const locale = cookieStore.get("locale")?.value ?? "en"; // the value must be serializable: const value = { locale, userName: null }; return ( <html lang={locale}> <body> <AppContext.Provider value={value}> {children} </AppContext.Provider> </body> </html> ); }

components/UserGreeting.tsx ("use client")

javascript
"use client"; import { useContext } from "react"; import { AppContext } from "@/shared/context"; export function UserGreeting() { const { locale, userName } = useContext(AppContext); return <p>{userName ? `Hi, ${userName}!` : `Locale: ${locale}`}</p>; }

Here the Provider on the server hands out value, and the client component reads it via useContext.


Practical tips

  • Split contexts by area (locale/auth/theme) to avoid unnecessary re-renders of client consumers.
  • Don't put secrets in context: anything available to the client is considered "public". Keep access logic on the server, and pass only "safe" derived values into the context.
  • Memoize large values on the client side (if the Provider is a client one) to avoid triggering unnecessary re-renders (useMemo for value).
  • For client-side dynamics (theme, toggles) wrap the relevant part of the tree with a client Provider. This won't affect the server components above it.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.