docs/pro/react-server-components/rendering-flow.md
This document explains the rendering flow of React Server Components (RSC) in React on Rails Pro.
In a React Server Components project, there are three distinct types of bundles, each running in a different environment with different constraints:
react-server condition and React server file aliases so the runtime uses RSC-specific code paths and shares one React server package instance across the renderer and app Server Components.target: 'node' in rscWebpackConfig.js is a build-time setting only; it does not grant the runtime VM access to host Node.js globals. Use supportModules, additionalContext, or bundled imports for any globals your RSC code needs.vm.createContext(), which has no global require() and lacks many Node.js/browser globals (see Bundle Architecture Reference below)'use client' directive becomes an entry pointUnderstanding the runtime differences between the three bundles is critical for avoiding hard-to-debug errors. The server bundle and RSC bundle look similar in webpack configuration, and the node renderer executes both through isolated VM contexts:
| Client Bundle | Server Bundle (SSR) | RSC Bundle | |
|---|---|---|---|
| Webpack config | clientWebpackConfig.js | serverWebpackConfig.js | rscWebpackConfig.js |
| Runtime | Browser | Node renderer VM context (vm.createContext) | Node renderer VM context (vm.createContext), used for RSC payload generation |
| Node builtins | Use resolve.fallback: false to omit | Use resolve.fallback: false (NOT externals, unless supportModules is enabled) | Use resolve.fallback: false (NOT externals, unless supportModules is enabled); do not assume host Node.js globals are visible in the VM |
require() | N/A | Not available by default. Webpack externals can call CommonJS require when supportModules is enabled or additionalContext is a plain object. | Not available by default. Webpack externals can call CommonJS require when supportModules is enabled or additionalContext is a plain object. |
| CSS extraction | Yes | No (exportOnlyLocals) | No |
| Isolated build env var | CLIENT_BUNDLE_ONLY | SERVER_BUNDLE_ONLY | RSC_BUNDLE_ONLY |
| Missing globals | N/A | MessageChannel, fetch, etc. (see troubleshooting) | Same VM global rules as the server bundle. supportModules covers common globals, but not fetch, Headers, Request, Response, AbortController, or AbortSignal by default. |
Key pitfall -- externals vs resolve.fallback in the server and RSC bundles:
The server bundle and RSC bundle both run in VM sandboxes that have no global require() function by default. Webpack's externals generates require('path') calls in the output, which will crash with require is not defined when the renderer executes the bundle as raw script. Instead, use resolve.fallback: { path: false, fs: false, stream: false } to tell webpack to omit these modules from the bundle.
externals can work when the renderer enables CommonJS execution mode. CommonJS execution mode is enabled by either:
supportModules: true (enables the mode regardless of additionalContext), oradditionalContext set to any plain object — including an empty {}. Pass additionalContext: null if you do not want additionalContext itself to opt into the mode.When CommonJS execution mode is active:
require() becomes available inside the bundle and webpack externals callbacks resolve correctly.require exposed to the bundle is the renderer host's require (the same require the launch file uses). It is passed in directly with no sandboxing, allowlist, or custom resolver — the renderer does not expose a hook for restricting which modules the bundle can load. Bundle code can load any module installed on the renderer host, not only modules included in the upload. A filtered additionalContext cannot narrow this; additionalContext controls what globals are injected, not what require can resolve.additionalContext does not inject a global require by itself; it just opts into the mode and injects only the globals you pass.Even with CommonJS execution mode enabled, resolve.fallback remains the safer default. The client bundle also uses resolve.fallback to omit Node builtins that don't exist in the browser.
[!WARNING] When CommonJS execution mode is active (
supportModules: true, oradditionalContextset to any plain object — including an empty{}), the bundle'srequire()is the renderer host'srequire. Bundle code can load any module installed on the host -- includingfs,child_process, and any other npm package present in the renderer'snode_modules. In multi-tenant or shared-renderer deployments where bundle uploads come from multiple sources, accept only trusted bundles in this mode. For example, restrict the bundle upload endpoint to authenticated administrators, verify a cryptographic checksum before loading the bundle, or run the renderer process under a restricted OS user without access to sensitive directories.
[!NOTE]
rscWebpackConfig.jsstill targets'node'for build-time module resolution. That webpack target is separate from the isolated VM context that executes the uploaded RSC bundle at runtime.
For fetch, Headers, Request, Response, AbortController, and AbortSignal, see Node Renderer JavaScript Configuration.
Traditional SSR without RSC is the simpler server-bundle-to-HTML path covered in the Node Renderer and Streaming SSR docs. The RSC path adds the RSC bundle and an embedded RSC payload:
<p align="center"> </p>The sequence below traces the same interaction over time.
When a request is made to a page using React Server Components, the following optimized sequence occurs:
Initial Request Processing:
stream_react_component helper is called in the viewHTML Rendering with RSC Payload:
Client Hydration:
This approach offers significant advantages:
To learn more about how to render React Server Components inside client components, see React Server Components Inside Client Components.