Server Component Module Graph
A Server Component module graph classifies modules into server and client dependency graphs based on component boundaries and import relationships. Understanding the graph explains why a module can run only on the server, why client dependencies become browser-delivered code, and where the server/client boundary must be placed.
In a React Server Components architecture, the module graph describes how source files depend on one another through imports. The build system analyzes those dependencies together with server/client boundaries to determine which modules belong to the server graph and which must be available to the client.
A module marked with a client boundary, commonly using the `"use client"` directive, becomes an entry point into the client graph. Its imported dependencies are treated as client-side dependencies as well, subject to the framework's module-graph rules. A server component can import other server-compatible modules, but it cannot directly import a client-only module without crossing the supported component boundary.
This graph is different from the rendered component tree. The component tree describes runtime composition of components, while the module graph describes static relationships between files and their imports. A single module can therefore affect many components because multiple files may depend on it.
The separation lets the framework keep server-only implementation details out of browser bundles while sending the browser the JavaScript needed for interactive client components. It also means that placing a client boundary too high in the dependency graph can cause substantially more code to become part of the client graph.
For interviews, focus on the dependency direction: a client entry point pulls its reachable client-compatible imports into the client side of the graph, while server-only code remains in the server graph. The exact bundling, manifest, and serialization mechanisms depend on the framework and build tooling.
Key points
- The module graph is based primarily on imports and server/client boundaries, not the rendered UI tree.
- `"use client"` establishes a client entry boundary in React Server Components implementations.
- Dependencies reachable from a client entry generally become part of the client dependency graph.
- Server-only modules should not leak into browser-delivered code.
- Moving a client boundary upward can increase the amount of code shipped to the browser.
- Module-graph rules and generated artifacts vary by framework and build tool.
Interview questions
What is a Server Component module graph?
It is the dependency graph produced by analyzing modules and their imports in an application using Server Components. The build system uses server/client boundaries to determine which modules belong on the server and which need to be available to the browser. A client boundary causes its reachable client-compatible dependencies to become relevant to the client graph. This separation helps keep server-only implementation out of browser bundles.
What happens when a module has a `"use client"` directive?
The module becomes a client entry point in React Server Components implementations that use this directive. Its imports are analyzed as part of the client dependency graph, subject to the framework's rules. This can cause more code to be included in browser-side bundles than just the entry file itself. Therefore, client boundaries should generally be placed where interactivity actually begins.
Why can't a client module freely import server-only code?
Browser code cannot execute dependencies that require a server-only environment, such as private server resources or server-side runtime APIs. If such a dependency crossed into the client graph, the build could either reject the dependency or otherwise require framework-specific handling. Server Components therefore use explicit boundaries to keep server-only code on the server. Data and behavior must cross the boundary through mechanisms supported by the framework.
How can client boundary placement affect performance?
A client boundary determines an entry into the client dependency graph, so placing it higher can make a larger portion of the dependency graph browser-relevant. That can increase JavaScript transfer, parsing, and execution work. Keeping interactive behavior in smaller client islands can allow more surrounding code to remain server-rendered. The actual optimization depends on the framework's bundler and chunking strategy, so bundle analysis should verify the effect.
How does the module graph differ from the rendered component tree?
The module graph is a static structure formed from module relationships such as imports. The component tree is the runtime or conceptual hierarchy of rendered components. One module can be imported by many other modules, so its graph position does not correspond one-to-one with a single component-tree node. Understanding both is important because a boundary in the module graph can affect the JavaScript available to an entire rendered portion of an application.