Skip to content

Conversation

@kirchoni
Copy link

@kirchoni kirchoni commented May 1, 2025

This RFC proposes reintroducing Server Context for React Server Components to provide a way to share values across components (including async components) without prop drilling.

Motivation

Server Components introduce async rendering patterns which seems to require a specialized context solution. The absence of such a mechanism causes several problems:

  1. Prop drilling becomes unavoidable - Even components that don't need certain data must receive and forward it
  2. Component composition becomes difficult - Deep hierarchies require threading props through every level
  3. Third-party libraries face unique challenges - UI component libraries cannot use global state and need self-contained ways to share configuration

This is especially problematic for component library authors who:

  • Cannot rely on global application state
  • Need to share configuration across complex component hierarchies
  • Must remain encapsulated and independent of application-specific globals

Proposed Solution

A set of APIs specifically designed for Server Components:

function createServerContext<T>(defaultValue: T, displayName?: string): ServerContext<T>;
function useServerContext<T>(context: ServerContext<T>): T;

Implementation

The implementation leverages Node.js's AsyncLocalStorage to maintain context across async boundaries:

  1. Server Context uses a distinct $$typeof symbol (REACT_SERVER_CONTEXT_TYPE)
  2. Values are stored in a Map within AsyncLocalStorage
  3. The API maintains React's familiar Provider pattern while handling async operations seamlessly

AsyncLocalStorage is already used successfully in React for resource resolution in server rendering, making it a proven solution for this purpose.

Key Use Cases

  • Component configuration - Complex UI components (DataGrids, etc.) can share settings across their hierarchy
  • Multi-tenant UI - Different sections of an app can use different theme/locale settings
  • Server resources - Authentication, request data, and database connections can be available throughout the tree

Performance Considerations

AsyncLocalStorage has minimal overhead and is optimized in Node.js:

  • Creates small, short-lived map instances
  • Only updates at render time, not during request processing
  • Offers better performance characteristics than alternatives like prop drilling

Drawbacks

  • Server-only API - requires distinct patterns from client components
  • Requires AsyncLocalStorage or equivalent in the runtime
  • Adds complexity to the React API surface

Alternatives Considered

  1. Prop drilling (status quo)
  2. Global mutable state (violates React principles)
  3. Function composition (deviates from component model)
  4. Client components with regular Context (loses RSC benefits)

Unresolved Questions

  1. Serialization strategy for hydration
  2. Interaction with Suspense boundaries
  3. Support for non-Node.js environments
  4. Potential bridge mechanisms to Client Context

References

@simprl
Copy link

simprl commented May 2, 2025

Thank you for raising this important topic and creating this pull request!

I completely agree with the motivation section. Without a proper context solution, Server Components will not be practical to use. Consider a simple example: I have a library with components. When rendered in the header, they should receive one set of props, but when rendered in the body, a different one. In this scenario, the context isn't about state management, but rather about knowing exactly where in the component tree the component is rendered - thus avoiding unnecessary prop drilling.

Additionally, it might be beneficial to keep the naming consistent (without the word "Server") to facilitate seamless migration of components between server and client environments. If unified naming isn't feasible, I suggest focusing naming conventions around the meaning of contexts - specifically distinguishing between contexts "without state change" and "with state change." For instance, a name like createRenderContext would clearly indicate that the context provides information about the component’s position within the render tree, irrespective of state changes.

@ShanonJackson
Copy link

ShanonJackson commented Jun 7, 2025

Agreed, this kind of composition isn't possible with server components if Dropdown manages open/closed in its root component; MenuItem's have no way to close the dropdown (previously could pass down a function to close the dropdown down via context. This serves purely as an example for the composability motivation.

This forces you to make bad design decisions due to the inability to use context to share state in a sub-tree.

<Dropdown trigger={(ref) => <div ref={ref}>h</div>}>
    <Dropdown.MenuItem>Add</Dropdown.MenuItem>
    <Dropdown.MenuItem>Delete</Dropdown.MenuItem>
</Dropdown>

=====

There's an implementation people can use today for server context, albeit it's not pretty

import React from "react";
import { cache, ReactNode } from "react";

export type UserContext = {
    name: string;
}

const [get, set] = cache((): [() => UserContext , (ctx: UserContext ) => void] => {
	let ctx = {} as UserContext ;
	return [() => ctx, (value: UserContext ) => (ctx = value)];
})();
export const UserContextServerProvider = ({ value, children }: { value: UserContext ; children: ReactNode }) => {
       /* simulates React Context Provider */
	set(value);
	return <>{children}</>;
};
/* simulates use(Context) */
export const useUserServer= () => get();

====

My issue with this proposal is that it's server only. I think the best and only solution is to create a solution that allows us to create context on the server that can be used on the client and therefore caters to more use-cases and doesn't require 2 api's and 2 mental models. This API would only allow JSON serializable contexts (same RSC constraint) and put the context in a script tag similarly to how Suspense does component swaps today for out of order streaming.

The reason why I think a isomorphic context solution is required is because it will support use cases like this.

<Graph data={data}> {/* server component */}
      <Lines /> {/* server component */}
       <Lines.Tooltip/> {/* client component */}
</Graph>

Where Lines and Lines.Tooltip can both access data via context ; This is merely an example but showcases why isomorphic context allows for better composability

===

TL;DR Isomorphic context solves the points in the motivation section, keeps the API the same (introduces no new APIs), Introduces little or no additional cognitive overhead for reasoning about who can access context where. Finally it has greater flexibility about what you can build with it.

@github-actions
Copy link

github-actions bot commented Sep 5, 2025

This pull request has been automatically marked as stale. If this pull request is still relevant, please leave any comment (for example, "bump"), and we'll keep it open. We are sorry that we haven't been able to prioritize reviewing it yet. Your contribution is very much appreciated.

@github-actions github-actions bot added the Resolution: Stale Automatically closed due to inactivity label Sep 5, 2025
@kirchoni
Copy link
Author

kirchoni commented Sep 5, 2025

uhm...bump, I guess

@github-actions github-actions bot removed the Resolution: Stale Automatically closed due to inactivity label Sep 5, 2025
@souporserious
Copy link

souporserious commented Sep 11, 2025

I've been working with Server Components since they first shipped and have used them exclusively in my library renoun. As detailed in this thread already, a first‑class server context in React would benefit the RSC ecosystem in several ways. This is currently where I see the benefits of reintroducing createServerContext and where it would help in renoun's case:

  • Top‑level configuration – renoun currently has a RootProvider that relies on overriding globalThis in development to propagate configuration when navigating pages. A native server context would let RootProvider pass its settings through createServerContext, removing the custom cache and global state entirely. This is an example of how I capture configuration right now:
<RootProvider git="souporserious/renoun" siteUrl="https://renoun.dev" theme="nord">
  ...
</RootProvider>

The benefits of this are that we can do other orchestration within this component like set up theme/locale providers in one place based on the incoming configuration. This keeps everything colocated and avoids requiring a separate config file.

  • Component composition – renoun also has a Tokens component that communicates with CodeBlock, and we ship a custom context built on AsyncLocalStorage. The AsyncLocalStorage API is node‑specific and can drop values across async boundaries, making composition brittle. Server context would provide a reliable, per‑request channel for propagating data to descendants.
<pre>
  <Tokens>let count = 0;</Tokens>
</pre>

<CodeBlock path="components/Button.tsx"> // reads file contents and passes down to `Tokens` component
  <Toolbar /> // gets the name from the path to display
  <Tokens />
</CodeBlock>
  • Framework consistency – It seems frameworks are experimenting with their own APIs (see Remix’s discussion). This forces library authors to ship framework‑specific code, undermining portability for server component libraries. A standardized React server context would keep libraries composable and bundler‑agnostic.

React Server Components have been an amazing addition to React, but for library authors specifically we need better control of moving around at least short bits of data like configuration around where prop drilling isn't feasible as we've seen with Client Components.

@kirchoni
Copy link
Author

kirchoni commented Sep 16, 2025

@souporserious - this PR suggests server context through AsyncLocalStorage, so it's still somewhat node-specific, even tho it implements it "inside" react, as opposed to trying to achieve it through the component's composition.

@ShanonJackson
Copy link

If we serialize some 'facts' about hydration to js snippets; Like swapping out of order Suspense elements back in order. Is there no precedence to serialize context into a json object and into a script/js snippet?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants