Symptom
In the Pro dummy app on main, both react-client-manifest.json and react-server-client-manifest.json are not written by webpack. Every webpack compilation logs:
WARNING in Client runtime at react-on-rails-rsc/client was not found.
React Server Components module map file react-client-manifest.json was not created.
Same warning for react-server-client-manifest.json from the server bundle. As a downstream effect, every RSC page (/rsc_echo_props, /rsc_posts_page_over_http, etc.) errors at runtime with ENOENT: no such file or directory, open '.../react-client-manifest.json' and falls back to a broken client render.
How to reproduce
In a long-lived checkout where pnpm install has accumulated state across multiple webpack version bumps (i.e., not a fresh install):
cd react_on_rails_pro/spec/dummy
bundle exec rake react_on_rails:generate_packs
pnpm run build:dev
Both warnings fire; both manifest files are missing from public/webpack/development/ and ssr-generated/.
A fresh pnpm install (e.g., a brand-new worktree) does not reproduce, which has hidden the issue from CI and from anyone who tries the obvious "wipe node_modules and try again" remedy as their first step.
Root cause
The plugin in react-on-rails-rsc (path inside the package: dist/react-server-dom-webpack/cjs/react-server-dom-webpack-plugin.js:87,141) detects whether webpack is bundling its runtime by strict path equality:
const clientFileNameOnClient = require.resolve("../client.browser.js"),
clientFileNameOnServer = require.resolve("../client.node.js");
// ...
parser.hooks.program.tap("React Server Plugin", () => {
const module = parser.state.module;
if (module.resource === (_this.isServer ? clientFileNameOnServer : clientFileNameOnClient)
&& ((clientFileNameFound = !0), resolvedClientReferences))
/* set up dep block */
});
If the === never matches, clientFileNameFound stays false and the manifest is never emitted (line 169).
The match fails in this monorepo because two physically separate copies of react-on-rails-rsc@19.0.4 exist on disk, both at the exact same npm version but resolved against different peer-dep contexts. Direct probe output captured by injecting a console.error into the parser hook:
[RSC-PROBE] isServer=false
resource=/mnt/ssd/react_on_rails/node_modules/react-on-rails-rsc/dist/react-server-dom-webpack/client.browser.js
expected=/mnt/ssd/react_on_rails/react_on_rails_pro/spec/dummy/node_modules/.pnpm/react-on-rails-rsc@19.0.4_…_webpack@5.103.0/node_modules/react-on-rails-rsc/dist/react-server-dom-webpack/client.browser.js
match=false
Webpack imports the runtime from the workspace root copy; the plugin (loaded from the dummy's .pnpm/...webpack@5.103.0/... copy) computes require.resolve("../client.browser.js") from its own location and points to a different file. Both files are byte-identical.
Why we end up with two copies
pnpm-lock.yaml resolves react-on-rails-rsc@19.0.4 four separate ways, each tied to a different (react × react-dom × webpack) peer-dep combination:
react-on-rails-rsc@19.0.4(react-dom@19.2.3)(react@19.2.3)(webpack@5.105.2)
react-on-rails-rsc@19.0.4(react-dom@19.2.0)(react@19.2.0)(webpack@5.105.2)
react-on-rails-rsc@19.0.4(react-dom@19.2.3)(react@19.2.3)(webpack@5.104.1)
react-on-rails-rsc@19.0.4 (no peers)
The dummy itself uses webpack@5.103.0 (transitively via shakapacker@9.6.1). With node-linker=hoisted + shamefully-hoist=true (.npmrc), pnpm hoists one peer-dep variant to the workspace root and stashes the others under dummy/node_modules/.pnpm/.... The dummy's own resolution then ends up at a different path than the hoisted root copy that webpack happens to bundle from.
Two possible fix locations — assignee's call
I'm filing this assigned to me to decide between the two; both are defensible.
Option A — Fix the dummy app setup
Pin webpack to a single version via pnpm.overrides (or align all webpack/react/react-dom across workspace consumers) so pnpm produces only one peer-dep variant. Concretely, adding "webpack": "5.104.1" to overrides + a clean pnpm install reduces the four variants to one and the manifests are emitted on the next build.
Pro: isolated to dummy/workspace config; no upstream change.
Con: every monorepo that consumes react-on-rails-rsc and has more than one webpack version on its dep tree will hit this. We'd be papering over a real plugin fragility.
Option B — Fix react-on-rails-rsc plugin
Replace the strict module.resource === clientFileNameOnClient check with something that survives split installs — e.g., suffix-match on dist/react-server-dom-webpack/client.{browser,node}.js plus a walk up to package.json to verify name === 'react-on-rails-rsc'. Sketch:
function isReactOnRailsRSCRuntime(resource, isServer) {
if (!resource) return false;
const expectedSuffix = path.join('react-server-dom-webpack',
isServer ? 'client.node.js' : 'client.browser.js');
if (!resource.endsWith(expectedSuffix)) return false;
let dir = path.dirname(resource);
for (let i = 0; i < 10; i++) {
const pkg = path.join(dir, 'package.json');
if (fs.existsSync(pkg)) {
try { return JSON.parse(fs.readFileSync(pkg, 'utf8')).name === 'react-on-rails-rsc'; }
catch { return false; }
}
const parent = path.dirname(dir);
if (parent === dir) return false;
dir = parent;
}
return false;
}
Pro: plugin works correctly under any pnpm/yarn doppelganger configuration; one fix benefits every consumer.
Con: requires upstream change + release of react-on-rails-rsc.
Temporary workaround currently applied locally — DO NOT MERGE
To unblock visual inspection of issue #3211's reproduction page, I added "webpack": "5.104.1" to pnpm.overrides in the workspace root package.json and ran a clean pnpm install. This forces the doppelgangers to collapse to one copy and the manifests are emitted again. This change should not be merged to main — it's a local pin that papers over the real cause. Revert when this issue is properly resolved.
Suggested next steps for the assignee
- Decide A vs B (or both).
- If A: probably better to align webpack/react versions properly across workspace consumers rather than blunt-pinning, and restore the separate
react_on_rails/spec/dummy and react_on_rails_pro/spec/dummy peer-dep contexts that originally diverged. The actual versions in the workspace today are 5.103.0 (Pro dummy via shakapacker), 5.104.1 (root + OSS dummy), 5.105.2 (transitively).
- If B: file as a separate issue against
shakacode/react_on_rails_rsc and add a regression test. The plugin's existing tests likely all run in single-install scenarios.
Related
Symptom
In the Pro dummy app on
main, bothreact-client-manifest.jsonandreact-server-client-manifest.jsonare not written by webpack. Every webpack compilation logs:Same warning for
react-server-client-manifest.jsonfrom the server bundle. As a downstream effect, every RSC page (/rsc_echo_props,/rsc_posts_page_over_http, etc.) errors at runtime withENOENT: no such file or directory, open '.../react-client-manifest.json'and falls back to a broken client render.How to reproduce
In a long-lived checkout where
pnpm installhas accumulated state across multiple webpack version bumps (i.e., not a fresh install):Both warnings fire; both manifest files are missing from
public/webpack/development/andssr-generated/.A fresh
pnpm install(e.g., a brand-new worktree) does not reproduce, which has hidden the issue from CI and from anyone who tries the obvious "wipe node_modules and try again" remedy as their first step.Root cause
The plugin in
react-on-rails-rsc(path inside the package:dist/react-server-dom-webpack/cjs/react-server-dom-webpack-plugin.js:87,141) detects whether webpack is bundling its runtime by strict path equality:If the
===never matches,clientFileNameFoundstaysfalseand the manifest is never emitted (line 169).The match fails in this monorepo because two physically separate copies of
react-on-rails-rsc@19.0.4exist on disk, both at the exact same npm version but resolved against different peer-dep contexts. Direct probe output captured by injecting aconsole.errorinto the parser hook:Webpack imports the runtime from the workspace root copy; the plugin (loaded from the dummy's
.pnpm/...webpack@5.103.0/...copy) computesrequire.resolve("../client.browser.js")from its own location and points to a different file. Both files are byte-identical.Why we end up with two copies
pnpm-lock.yamlresolvesreact-on-rails-rsc@19.0.4four separate ways, each tied to a different (react × react-dom × webpack) peer-dep combination:The dummy itself uses
webpack@5.103.0(transitively viashakapacker@9.6.1). Withnode-linker=hoisted+shamefully-hoist=true(.npmrc), pnpm hoists one peer-dep variant to the workspace root and stashes the others underdummy/node_modules/.pnpm/.... The dummy's own resolution then ends up at a different path than the hoisted root copy that webpack happens to bundle from.Two possible fix locations — assignee's call
I'm filing this assigned to me to decide between the two; both are defensible.
Option A — Fix the dummy app setup
Pin webpack to a single version via
pnpm.overrides(or align all webpack/react/react-dom across workspace consumers) so pnpm produces only one peer-dep variant. Concretely, adding"webpack": "5.104.1"to overrides + a cleanpnpm installreduces the four variants to one and the manifests are emitted on the next build.Pro: isolated to dummy/workspace config; no upstream change.
Con: every monorepo that consumes
react-on-rails-rscand has more than one webpack version on its dep tree will hit this. We'd be papering over a real plugin fragility.Option B — Fix
react-on-rails-rscpluginReplace the strict
module.resource === clientFileNameOnClientcheck with something that survives split installs — e.g., suffix-match ondist/react-server-dom-webpack/client.{browser,node}.jsplus a walk up topackage.jsonto verifyname === 'react-on-rails-rsc'. Sketch:Pro: plugin works correctly under any pnpm/yarn doppelganger configuration; one fix benefits every consumer.
Con: requires upstream change + release of
react-on-rails-rsc.Temporary workaround currently applied locally — DO NOT MERGE
To unblock visual inspection of issue #3211's reproduction page, I added
"webpack": "5.104.1"topnpm.overridesin the workspace rootpackage.jsonand ran a cleanpnpm install. This forces the doppelgangers to collapse to one copy and the manifests are emitted again. This change should not be merged tomain— it's a local pin that papers over the real cause. Revert when this issue is properly resolved.Suggested next steps for the assignee
react_on_rails/spec/dummyandreact_on_rails_pro/spec/dummypeer-dep contexts that originally diverged. The actual versions in the workspace today are5.103.0(Pro dummy via shakapacker),5.104.1(root + OSS dummy),5.105.2(transitively).shakacode/react_on_rails_rscand add a regression test. The plugin's existing tests likely all run in single-install scenarios.Related
'use client'flickers/FOUCs because client manifest tracks only JS, not CSS #3211 (RSC FOUC) — the missing manifests blocked visual reproduction of that bug.