fix(pro): preserve react-on-rails-rsc/client.browser in client bundle (#3366)#3368
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughAdds a top-level side-effect import of ChangesPro RSC Client Manifest Restoration
Sequence DiagramsequenceDiagram
participant ProClient as wrapServerComponentRenderer.client
participant Webpack
participant RSCPlugin as RSCWebpackPlugin
participant FS as Filesystem
ProClient->>Webpack: include side-effect import 'react-on-rails-rsc/client.browser'
Webpack->>RSCPlugin: client runtime present in module graph
RSCPlugin->>FS: emit react-client-manifest.json
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
knip.tsESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code ReviewOverviewMinimal, targeted fix for a real regression (#3366). The root cause analysis is solid: RSCWebpackPlugin does an exact-path scan of each parsed module rather than a full transitive-graph walk, so a three-level transitive import can silently disappear under tree-shaking or custom webpack transformations. Adding a direct side-effect import is the correct surgical fix. Strengths
Issues1. Test silently passes when
|
size-limit report 📦
|
Greptile SummaryThis PR fixes a 16.7.0-rc.0 regression where
Confidence Score: 4/5Safe to merge; the change is a one-line side-effect import with thorough comments and a new regression test. The fix is minimal and well-understood: a single side-effect import that makes the module graph more robust. The only rough edge is in the new test — the second The test file Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["wrapServerComponentRenderer/client.tsx"] -->|"side-effect import (NEW)"| B["react-on-rails-rsc/client.browser"]
A -->|transitive chain| C["getReactServerComponent.client.ts"]
C -->|transitive| B
B --> D["RSCWebpackPlugin\nscans module graph for\nclient.browser.js resource"]
D -->|"clientFileNameFound = true"| E["emits react-client-manifest.json ✅"]
D -->|"clientFileNameFound = false\n(if chain severed)"| F["manifest silently dropped ❌\nNode Renderer → ENOENT"]
Reviews (1): Last reviewed commit: "fix(pro): preserve react-on-rails-rsc/cl..." | Re-trigger Greptile |
Address PR #3368 review feedback: the previous early `return` caused Jest to count the lib-output regression test as passing (not skipped) when `lib/` had not been built, providing no CI signal. Use conditional `it`/`it.skip` registration so the report shows the test as explicitly skipped on a fresh checkout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review feedback addressedBoth review threads (from Fix (f895ff5): I (Claude Code) switched to conditional registration Verified locally: the suite still passes ( |
| const libExists = fs.existsSync(libPath); | ||
| // lib is only built before publish or after `pnpm run build`. When absent the | ||
| // test is explicitly skipped (visible in the report) rather than silently green. | ||
| (libExists ? it : it.skip)('keeps the same runtime present in the compiled lib output', () => { |
There was a problem hiding this comment.
The libExists branch will always be skipped in CI because lib/ is built only at publish time and isn't committed. That means the CI pipeline gets zero coverage of whether the TypeScript compiler preserves the import in the compiled output — the test silently passes (as a skip) even if the tsc step strips the side-effect import.
Two options worth considering:
-
Add a
pnpm run build && <this test>step to CI so the lib test actually runs at least once before a release. Thefull-cilabel on this PR suggests a broad CI matrix is already in play — could be the right place to add it. -
Remove the lib test entirely if a build step isn't feasible, and rely on the src test + the published package's pre-release smoke test. An always-skipped test provides false assurance — it appears in the report but catches nothing.
| // Side-effect import: keeps `react-on-rails-rsc/client.browser` in the webpack | ||
| // module graph for the client bundle so RSCWebpackPlugin (which scans every | ||
| // parsed module for this exact resource) can detect the client runtime and | ||
| // emit `react-client-manifest.json`. Without this direct import, the plugin | ||
| // relies on a 3-level transitive chain | ||
| // (`wrapServerComponentRenderer/client` → `getReactServerComponent.client` | ||
| // → `react-on-rails-rsc/client.browser`). Any tooling that severs that chain | ||
| // (tree-shaking, transpilers, NormalModuleReplacement, custom externals) | ||
| // silently drops the manifest and breaks RSC hydration on the renderer. | ||
| import 'react-on-rails-rsc/client.browser'; |
There was a problem hiding this comment.
This comment is warranted (the WHY here is genuinely non-obvious), but it's duplicated almost verbatim in three places: here, in the test file's describe block, and in the PR description/CHANGELOG. The source comment is the most load-bearing location; the test comment can just point back to the issue number.
Suggested condensed version:
| // Side-effect import: keeps `react-on-rails-rsc/client.browser` in the webpack | |
| // module graph for the client bundle so RSCWebpackPlugin (which scans every | |
| // parsed module for this exact resource) can detect the client runtime and | |
| // emit `react-client-manifest.json`. Without this direct import, the plugin | |
| // relies on a 3-level transitive chain | |
| // (`wrapServerComponentRenderer/client` → `getReactServerComponent.client` | |
| // → `react-on-rails-rsc/client.browser`). Any tooling that severs that chain | |
| // (tree-shaking, transpilers, NormalModuleReplacement, custom externals) | |
| // silently drops the manifest and breaks RSC hydration on the renderer. | |
| import 'react-on-rails-rsc/client.browser'; | |
| // Side-effect import: RSCWebpackPlugin only emits react-client-manifest.json when it finds | |
| // react-on-rails-rsc/client.browser in the parsed module graph. A direct import here | |
| // guarantees that regardless of how downstream tooling handles transitive chains. See #3366. |
Review: fix(pro): preserve react-on-rails-rsc/client.browser in client bundle (#3366)SummaryTargeted, well-reasoned fix. The root cause is clear (RSCWebpackPlugin's module-graph scan requires a direct-import path to Correctness of the fix ✅Adding registerServerComponent/client.tsx — consider a defensive import there tooEven after this fix, if a user's pack entry is Test approach
|
| */ | ||
|
|
||
| import { createRequire } from 'module'; | ||
|
|
There was a problem hiding this comment.
This file uses ESM import syntax at the top but then relies on __filename, which is a CJS-only global. It works today because ts-jest's createJsWithTsPreset transforms the ESM import to require before running, making __filename available — but it would silently break if the Jest config ever switches to native ESM (--experimental-vm-modules / extensionsToTreatAsEsm).
Pick one style consistently:
| const { createRequire } = require('module'); | |
| const nodeRequire = createRequire(__filename); |
or, if you want pure ESM:
import { createRequire } from 'module';
const nodeRequire = createRequire(import.meta.url);| }; | ||
|
|
||
| describe('wrapServerComponentRenderer client manifest emission', () => { | ||
| it('keeps the RSC client runtime visible to RSCWebpackPlugin', async () => { |
There was a problem hiding this comment.
No explicit timeout is set for this test. Jest's default async test timeout is 5 seconds, and a cold webpack compilation (no cache, first CI run) can easily exceed that, causing a spurious timeout failure on slow runners.
Add a suite-level override at the top of the describe block (or before it):
jest.setTimeout(30_000);Or pass the timeout as the third it argument:
| it('keeps the RSC client runtime visible to RSCWebpackPlugin', async () => { | |
| it('keeps the RSC client runtime visible to RSCWebpackPlugin', async () => { |
→ it('keeps the RSC client runtime visible to RSCWebpackPlugin', async () => { ... }, 30_000);
| // Side-effect import: keeps `react-on-rails-rsc/client.browser` in the webpack | ||
| // module graph for the client bundle so RSCWebpackPlugin (which scans every | ||
| // parsed module for this exact resource) can detect the client runtime and | ||
| // emit `react-client-manifest.json`. Without this direct import, the plugin | ||
| // relies on a 3-level transitive chain | ||
| // (`wrapServerComponentRenderer/client` → `getReactServerComponent.client` | ||
| // → `react-on-rails-rsc/client.browser`). Any tooling that severs that chain | ||
| // (tree-shaking, transpilers, NormalModuleReplacement, custom externals) |
There was a problem hiding this comment.
This 9-line comment restates information that's already in the regression test's header comment and the PR description. The project convention is to comment only on the non-obvious WHY; a shorter version keeps the signal without the noise:
| // Side-effect import: keeps `react-on-rails-rsc/client.browser` in the webpack | |
| // module graph for the client bundle so RSCWebpackPlugin (which scans every | |
| // parsed module for this exact resource) can detect the client runtime and | |
| // emit `react-client-manifest.json`. Without this direct import, the plugin | |
| // relies on a 3-level transitive chain | |
| // (`wrapServerComponentRenderer/client` → `getReactServerComponent.client` | |
| // → `react-on-rails-rsc/client.browser`). Any tooling that severs that chain | |
| // (tree-shaking, transpilers, NormalModuleReplacement, custom externals) | |
| // Side-effect import required: RSCWebpackPlugin only emits react-client-manifest.json when it | |
| // finds this module in the client graph. A transitive chain is not reliable under tree-shaking. | |
| import 'react-on-rails-rsc/client.browser'; |
Code ReviewOverviewThis PR fixes a fragile transitive dependency on The fix is correct and well-targeted. Issues (see inline comments)
What's Good
VerdictCore fix and test strategy are solid. Address the Jest timeout before merging to avoid CI flakiness on slow runners. The ESM/CJS mixing is low-risk under the current transform setup but worth cleaning up for future-proofing. |
Add a side-effect import of `react-on-rails-rsc/client.browser` at the
top of `wrapServerComponentRenderer/client.tsx`. RSCWebpackPlugin scans
every parsed module's resource path for an exact match against
`require.resolve("../client.browser.js")` and only emits
`react-client-manifest.json` when it finds one. Previously the client
runtime was reachable only through a three-level transitive chain
(`wrapServerComponentRenderer/client` → `getReactServerComponent.client`
→ `react-on-rails-rsc/client.browser`); any tooling that severed a link
in that chain silently dropped the manifest and broke RSC hydration on
the Pro Node Renderer with a misleading "Client runtime ... was not
found" warning.
Add a structural regression test that fails if the side-effect import
is removed from either the source or the compiled lib output.
Fixes #3366
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address PR #3368 review feedback: the previous early `return` caused Jest to count the lib-output regression test as passing (not skipped) when `lib/` had not been built, providing no CI signal. Use conditional `it`/`it.skip` registration so the report shows the test as explicitly skipped on a fresh checkout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3013f9d to
8952812
Compare
Code ReviewOverall: Solid, targeted fix with thorough regression tests. Two minor issues noted below. What this PR doesAdds a direct side-effect import of CorrectnessThe fix is correct and minimal. Test qualityTwo-pronged coverage is appropriate here:
The conditional Minor issues
Non-blocking notes
|
|
|
||
| import { createRequire } from 'module'; | ||
|
|
||
| const nodeRequire = createRequire(__filename); |
There was a problem hiding this comment.
ESM portability: __filename is a CommonJS-only global. This works today because ts-jest transforms the file to CJS before running it, but would throw ReferenceError: __filename is not defined if the project ever moves to true ESM Jest (--experimental-vm-modules).
The ESM-native equivalent needs no __filename at all:
| const nodeRequire = createRequire(__filename); | |
| const nodeRequire = createRequire(import.meta.url); |
| }; | ||
|
|
||
| describe('wrapServerComponentRenderer client manifest emission', () => { | ||
| it('keeps the RSC client runtime visible to RSCWebpackPlugin', async () => { |
There was a problem hiding this comment.
Webpack compilation can occasionally exceed Jest's default 5 s timeout on loaded CI runners, causing a flaky failure. Consider adding an explicit timeout:
| it('keeps the RSC client runtime visible to RSCWebpackPlugin', async () => { | |
| it('keeps the RSC client runtime visible to RSCWebpackPlugin', async () => { |
…with either jest.setTimeout(30_000) at the top of the describe block, or a third argument }, 30_000) at the closing of this it call.
The wrapServerComponentRenderer client manifest test uses webpack's NormalModuleReplacementPlugin to swap in a stub fixture via a runtime path string, which knip cannot trace statically. Mirror the existing ignore pattern from the pro-node-renderer workspace so the fixture isn't flagged as unused. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code ReviewOverviewThis PR fixes a regression in 16.7.0-rc.0 where What's Good
IssuesMedium: Missing Jest timeout in webpack compilation testThe manifest test ( Minor: Stub return type is loose
Nit: Skipped lib test is only as fresh as the last buildThe SummaryThe fix is correct and well-tested. The Jest timeout is the only real risk — webpack compilations in CI have caused flakiness in other projects when left at the default 5 s. Everything else is minor. |
| }); | ||
| }); |
There was a problem hiding this comment.
A full webpack compilation with swc-loader can easily exceed Jest's default 5 000 ms timeout on slower CI runners, producing a flaky Exceeded timeout failure unrelated to the actual fix. Consider bumping the timeout:
| }); | |
| }); | |
| describe('wrapServerComponentRenderer client manifest emission', () => { | |
| it('keeps the RSC client runtime visible to RSCWebpackPlugin', async () => { |
Either add jest.setTimeout(30_000); above this describe block, or pass a third timeout argument to the it() call:
it('keeps the RSC client runtime visible to RSCWebpackPlugin', async () => { ... }, 30_000);| @@ -0,0 +1,3 @@ | |||
| export default function getReactServerComponent() { | |||
| return () => Promise.resolve(null); | |||
There was a problem hiding this comment.
The return type here (() => Promise<null>) doesn't match the real module's signature (which returns a React component). This is fine because the stub is only used at webpack build time and never executed by Jest — but a short comment would make that clear to a future reader who wonders why the types don't align:
| return () => Promise.resolve(null); | |
| // Build-only stub: webpack replaces the real module via NormalModuleReplacementPlugin. | |
| // This function is never executed; only its presence in the module graph matters. | |
| return () => Promise.resolve(null); |
…-maintenance-doc * origin/main: docs: document RSC node renderer globals (#3221) fix(pro): preserve react-on-rails-rsc/client.browser in client bundle (#3366) (#3368) Allow Pro 16.7 to keep jwt 2.x compatibility (#3344) Plan: flagship 2026 React on Rails starter kit (#3357) (#3364) docs: clarify coupled gem/npm/lockfile upgrade for React on Rails Pro (#3369) chore(deps): bump fastify from 5.8.3 to 5.8.5 in the npm-security group across 1 directory (#3152) docs: clarify initial security triage ownership in SECURITY.md (#3337) docs: consolidate node renderer probe settings (#3257) (#3348) chore(ci): remove obsolete setup-node-with-retry action (#3352) chore: remove completed planning/analysis docs (#3361) test: stub stable VERSION in --pro install spec to unblock CI (#3363) docs(specs): add RC testing plan design spec (#3350) docs: point users at bin/shakapacker-config --doctor for bundler-config debugging (#3359) docs: polish RSC node renderer test recipe (#3273) (#3339) test: replace call_count stub pattern with and_return sequence (#2157) (#3353) docs: call out Inertia's incompatibility with client-side routers (#3356) docs: replace personal email with contact URL in Pro README (#3347) # Conflicts: # react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb
* origin/main: fix: add reactDomClientWarning helper for React 16/17 (#3137) (#3358) docs: document RSC node renderer globals (#3221) fix(pro): preserve react-on-rails-rsc/client.browser in client bundle (#3366) (#3368) Allow Pro 16.7 to keep jwt 2.x compatibility (#3344) Plan: flagship 2026 React on Rails starter kit (#3357) (#3364) docs: clarify coupled gem/npm/lockfile upgrade for React on Rails Pro (#3369) chore(deps): bump fastify from 5.8.3 to 5.8.5 in the npm-security group across 1 directory (#3152) docs: clarify initial security triage ownership in SECURITY.md (#3337) docs: consolidate node renderer probe settings (#3257) (#3348) chore(ci): remove obsolete setup-node-with-retry action (#3352) chore: remove completed planning/analysis docs (#3361) test: stub stable VERSION in --pro install spec to unblock CI (#3363) docs(specs): add RC testing plan design spec (#3350) docs: point users at bin/shakapacker-config --doctor for bundler-config debugging (#3359) docs: polish RSC node renderer test recipe (#3273) (#3339) test: replace call_count stub pattern with and_return sequence (#2157) (#3353) docs: call out Inertia's incompatibility with client-side routers (#3356) # Conflicts: # .github/workflows/pro-lint.yml
…#3366) (#3368) ## Summary Fixes [#3366](#3366) — a 16.7.0-rc.0 regression where the RSC client manifest (`react-client-manifest.json`) is silently dropped from the Pro Node Renderer upload, breaking server-component rendering with `ENOENT` on the renderer side. - Add a side-effect import of `react-on-rails-rsc/client.browser` at the top of `wrapServerComponentRenderer/client.tsx`. RSCWebpackPlugin scans every parsed module's resource path for an exact match against `require.resolve("../client.browser.js")` and only emits `react-client-manifest.json` when it finds one. Previously the client runtime was reachable only through a three-level transitive chain (`wrapServerComponentRenderer/client` → `getReactServerComponent.client` → `react-on-rails-rsc/client.browser`); any tooling that severed a link in that chain silently dropped the manifest and produced the misleading `Client runtime at react-on-rails-rsc/client was not found` warning. The direct import keeps the runtime resource in the client module graph regardless of how downstream tooling handles transitive default imports. - Add a structural regression test that fails if the side-effect import is removed from either the source or the compiled lib output. - Add a webpack-level regression test that compiles `wrapServerComponentRenderer/client.tsx` with the old transitive `getReactServerComponent.client` edge replaced by a stub, then asserts `RSCWebpackPlugin` still emits `react-client-manifest.json`. This proves the direct import itself preserves the manifest. ## Why Issue #3366 reports that the warning + missing manifest first appears when the consuming app upgrades to `react_on_rails_pro` / `react-on-rails-pro` / `react-on-rails-pro-node-renderer` `16.7.0-rc.0` from `16.6.0`, even with `react-on-rails-rsc@19.0.4` and `shakapacker@10.0.0` unchanged. The actual chain in source/lib is intact between both versions, and the issue does not reproduce on the in-tree dummy app or a clean clone of the tutorial PR — but the user's environment clearly does see `clientFileNameFound = false` from the upstream RSC plugin. The defensive direct import removes that fragility: even if a future transpiler/tree-shaker decides `getReactServerComponent.client` is "unused" or rewrites the path, the side-effect import keeps `client.browser` in the graph and the plugin always emits the manifest. ## Evaluation Confirmed the fix addresses the bug mechanism. The RSC plugin decides whether to write `react-client-manifest.json` by seeing the `react-on-rails-rsc/client.browser` module in the client compilation. The direct side-effect import is an appropriate fix because it changes build graph visibility without changing renderer runtime behavior. I verified the new behavior test is meaningful by temporarily removing the direct import: with the old transitive helper path stubbed out, webpack emitted the expected `Client runtime at react-on-rails-rsc/client was not found` warning and the test failed. Restoring the import made the same test pass and emit `react-client-manifest.json`. The Pro dummy build also emits both `react-client-manifest.json` and `react-server-client-manifest.json` successfully. ## Test plan - [x] `pnpm --filter react-on-rails-pro exec jest tests/wrapServerComponentRenderer.client.manifest.test.js --runInBand` passes - [x] Red check: temporarily removing `import 'react-on-rails-rsc/client.browser';` makes the new manifest test fail with the upstream missing-client-runtime warning - [x] `pnpm --filter react-on-rails-pro exec jest tests/wrapServerComponentRenderer.client.manifest.test.js tests/wrapServerComponentRenderer.client.imports.test.ts --runInBand` passes - [x] `pnpm --filter react-on-rails-pro exec jest tests/createReactOnRailsPro.test.ts --runInBand` passes - [x] `pnpm --filter react-on-rails-pro run test:non-rsc` passes: 7 suites, 58 tests - [x] `pnpm --filter react-on-rails-pro run type-check` passes - [x] `pnpm run lint` passes - [x] `(cd react_on_rails && bundle exec rubocop)` passes - [x] `(cd react_on_rails_pro && bundle exec rubocop --ignore-parent-exclusion)` passes; RuboCop prints deprecation warnings from dependencies but reports no offenses - [x] `pnpm exec prettier --check packages/react-on-rails-pro/tests/wrapServerComponentRenderer.client.manifest.test.js packages/react-on-rails-pro/tests/fixtures/rsc-manifest/getReactServerComponent.client.stub.ts` passes - [x] `RAILS_ENV=test NODE_ENV=development bundle exec bin/shakapacker` from `react_on_rails_pro/spec/dummy` passes and emits `react-client-manifest.json` plus `react-server-client-manifest.json` - [x] `git diff --check` passes - [x] Pre-commit and pre-push hooks passed while committing and pushing `3013f9d90` - [ ] Full-repo `pnpm start format.listDifferent` is blocked by an existing ignored `.context/plans/issue-3366-reproduction-plan.md` formatting issue; the PR files pass direct Prettier checks - [ ] Verify CI passes on this PR - [ ] Verify [shakacode/react-webpack-rails-tutorial#740](shakacode/react-webpack-rails-tutorial#740) unblocks once a new RC build with this fix is published 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches Pro RSC client entrypoint bundling to force inclusion of the RSC client runtime, which can affect build output and hydration behavior if misconfigured. Added tests reduce regression risk, but changes are in a critical RSC packaging path. > > **Overview** > Restores reliable React Server Components hydration for Pro by adding a **top-level side-effect import** of `react-on-rails-rsc/client.browser` in `wrapServerComponentRenderer/client.tsx`, ensuring `RSCWebpackPlugin` consistently emits `react-client-manifest.json` even when transitive imports are disrupted. > > Adds regression coverage: a structural test that enforces the presence of the side-effect import (source and built `lib` when available) and a webpack-level test that stubs `getReactServerComponent.client` to prove manifest emission still occurs. Updates `knip.ts` to ignore dynamically referenced test fixtures and records the fix in `CHANGELOG.md`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 1bb24cc. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Restored inclusion of the client runtime so the client manifest is emitted, preventing React Server Components hydration failures in certain build setups. * **Tests** * Added regression tests to ensure the client runtime import is preserved and the client manifest is emitted during bundling. * **Documentation** * Updated CHANGELOG with the fix. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/shakacode/react_on_rails/pull/3368?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* origin/main: (54 commits) Bump version to 16.7.0.rc.1 [codex] Prepare 16.7.0.rc.1 changelog (#3384) fix(tanstack-router): remove Suspense gate around RouterProvider during hydration (#3213) chore(deps): bump the npm-security group across 1 directory with 3 updates (#3370) fix: add reactDomClientWarning helper for React 16/17 (#3137) (#3358) docs: document RSC node renderer globals (#3221) fix(pro): preserve react-on-rails-rsc/client.browser in client bundle (#3366) (#3368) Allow Pro 16.7 to keep jwt 2.x compatibility (#3344) Plan: flagship 2026 React on Rails starter kit (#3357) (#3364) docs: clarify coupled gem/npm/lockfile upgrade for React on Rails Pro (#3369) chore(deps): bump fastify from 5.8.3 to 5.8.5 in the npm-security group across 1 directory (#3152) docs: clarify initial security triage ownership in SECURITY.md (#3337) docs: consolidate node renderer probe settings (#3257) (#3348) chore(ci): remove obsolete setup-node-with-retry action (#3352) chore: remove completed planning/analysis docs (#3361) test: stub stable VERSION in --pro install spec to unblock CI (#3363) docs(specs): add RC testing plan design spec (#3350) docs: point users at bin/shakapacker-config --doctor for bundler-config debugging (#3359) docs: polish RSC node renderer test recipe (#3273) (#3339) test: replace call_count stub pattern with and_return sequence (#2157) (#3353) ...
* origin/main: (54 commits) Bump version to 16.7.0.rc.1 [codex] Prepare 16.7.0.rc.1 changelog (#3384) fix(tanstack-router): remove Suspense gate around RouterProvider during hydration (#3213) chore(deps): bump the npm-security group across 1 directory with 3 updates (#3370) fix: add reactDomClientWarning helper for React 16/17 (#3137) (#3358) docs: document RSC node renderer globals (#3221) fix(pro): preserve react-on-rails-rsc/client.browser in client bundle (#3366) (#3368) Allow Pro 16.7 to keep jwt 2.x compatibility (#3344) Plan: flagship 2026 React on Rails starter kit (#3357) (#3364) docs: clarify coupled gem/npm/lockfile upgrade for React on Rails Pro (#3369) chore(deps): bump fastify from 5.8.3 to 5.8.5 in the npm-security group across 1 directory (#3152) docs: clarify initial security triage ownership in SECURITY.md (#3337) docs: consolidate node renderer probe settings (#3257) (#3348) chore(ci): remove obsolete setup-node-with-retry action (#3352) chore: remove completed planning/analysis docs (#3361) test: stub stable VERSION in --pro install spec to unblock CI (#3363) docs(specs): add RC testing plan design spec (#3350) docs: point users at bin/shakapacker-config --doctor for bundler-config debugging (#3359) docs: polish RSC node renderer test recipe (#3273) (#3339) test: replace call_count stub pattern with and_return sequence (#2157) (#3353) ... # Conflicts: # docs/oss/core-concepts/performance-benchmarks.md
* origin/main: Bump version to 16.7.0.rc.1 [codex] Prepare 16.7.0.rc.1 changelog (#3384) fix(tanstack-router): remove Suspense gate around RouterProvider during hydration (#3213) chore(deps): bump the npm-security group across 1 directory with 3 updates (#3370) fix: add reactDomClientWarning helper for React 16/17 (#3137) (#3358) docs: document RSC node renderer globals (#3221) fix(pro): preserve react-on-rails-rsc/client.browser in client bundle (#3366) (#3368) Allow Pro 16.7 to keep jwt 2.x compatibility (#3344) Plan: flagship 2026 React on Rails starter kit (#3357) (#3364) docs: clarify coupled gem/npm/lockfile upgrade for React on Rails Pro (#3369) chore(deps): bump fastify from 5.8.3 to 5.8.5 in the npm-security group across 1 directory (#3152) docs: clarify initial security triage ownership in SECURITY.md (#3337) docs: consolidate node renderer probe settings (#3257) (#3348) chore(ci): remove obsolete setup-node-with-retry action (#3352) chore: remove completed planning/analysis docs (#3361)
* origin/main: (26 commits) docs: define security-supported versions policy (#3261) (#3341) Bump version to 16.7.0.rc.2 Update CHANGELOG.md for 16.7.0.rc.2 (#3393) Fail release on unverifiable or Yarn-incompatible npm packages (#3387) Bump version to 16.7.0.rc.1 [codex] Prepare 16.7.0.rc.1 changelog (#3384) fix(tanstack-router): remove Suspense gate around RouterProvider during hydration (#3213) chore(deps): bump the npm-security group across 1 directory with 3 updates (#3370) fix: add reactDomClientWarning helper for React 16/17 (#3137) (#3358) docs: document RSC node renderer globals (#3221) fix(pro): preserve react-on-rails-rsc/client.browser in client bundle (#3366) (#3368) Allow Pro 16.7 to keep jwt 2.x compatibility (#3344) Plan: flagship 2026 React on Rails starter kit (#3357) (#3364) docs: clarify coupled gem/npm/lockfile upgrade for React on Rails Pro (#3369) chore(deps): bump fastify from 5.8.3 to 5.8.5 in the npm-security group across 1 directory (#3152) docs: clarify initial security triage ownership in SECURITY.md (#3337) docs: consolidate node renderer probe settings (#3257) (#3348) chore(ci): remove obsolete setup-node-with-retry action (#3352) chore: remove completed planning/analysis docs (#3361) test: stub stable VERSION in --pro install spec to unblock CI (#3363) ...
* origin/main: (31 commits) More precise heading in CHANGELOG (#3402) Update `pino` dependency range (#3401) Upgrade to latest pnpm v10 (#3400) docs: track CI_PNPM_FALLBACK_VERSION bumps in CONTRIBUTING (#3264) (#3346) docs(generator): record pnpm fallback bump policy and last-checked date (#3248) (#3328) docs: define security-supported versions policy (#3261) (#3341) Bump version to 16.7.0.rc.2 Update CHANGELOG.md for 16.7.0.rc.2 (#3393) Fail release on unverifiable or Yarn-incompatible npm packages (#3387) Bump version to 16.7.0.rc.1 [codex] Prepare 16.7.0.rc.1 changelog (#3384) fix(tanstack-router): remove Suspense gate around RouterProvider during hydration (#3213) chore(deps): bump the npm-security group across 1 directory with 3 updates (#3370) fix: add reactDomClientWarning helper for React 16/17 (#3137) (#3358) docs: document RSC node renderer globals (#3221) fix(pro): preserve react-on-rails-rsc/client.browser in client bundle (#3366) (#3368) Allow Pro 16.7 to keep jwt 2.x compatibility (#3344) Plan: flagship 2026 React on Rails starter kit (#3357) (#3364) docs: clarify coupled gem/npm/lockfile upgrade for React on Rails Pro (#3369) chore(deps): bump fastify from 5.8.3 to 5.8.5 in the npm-security group across 1 directory (#3152) ... # Conflicts: # CHANGELOG.md
Summary
Fixes #3366 — a 16.7.0-rc.0 regression where the RSC client manifest (
react-client-manifest.json) is silently dropped from the Pro Node Renderer upload, breaking server-component rendering withENOENTon the renderer side.react-on-rails-rsc/client.browserat the top ofwrapServerComponentRenderer/client.tsx. RSCWebpackPlugin scans every parsed module's resource path for an exact match againstrequire.resolve("../client.browser.js")and only emitsreact-client-manifest.jsonwhen it finds one. Previously the client runtime was reachable only through a three-level transitive chain (wrapServerComponentRenderer/client→getReactServerComponent.client→react-on-rails-rsc/client.browser); any tooling that severed a link in that chain silently dropped the manifest and produced the misleadingClient runtime at react-on-rails-rsc/client was not foundwarning. The direct import keeps the runtime resource in the client module graph regardless of how downstream tooling handles transitive default imports.wrapServerComponentRenderer/client.tsxwith the old transitivegetReactServerComponent.clientedge replaced by a stub, then assertsRSCWebpackPluginstill emitsreact-client-manifest.json. This proves the direct import itself preserves the manifest.Why
Issue #3366 reports that the warning + missing manifest first appears when the consuming app upgrades to
react_on_rails_pro/react-on-rails-pro/react-on-rails-pro-node-renderer16.7.0-rc.0from16.6.0, even withreact-on-rails-rsc@19.0.4andshakapacker@10.0.0unchanged. The actual chain in source/lib is intact between both versions, and the issue does not reproduce on the in-tree dummy app or a clean clone of the tutorial PR — but the user's environment clearly does seeclientFileNameFound = falsefrom the upstream RSC plugin. The defensive direct import removes that fragility: even if a future transpiler/tree-shaker decidesgetReactServerComponent.clientis "unused" or rewrites the path, the side-effect import keepsclient.browserin the graph and the plugin always emits the manifest.Evaluation
Confirmed the fix addresses the bug mechanism. The RSC plugin decides whether to write
react-client-manifest.jsonby seeing thereact-on-rails-rsc/client.browsermodule in the client compilation. The direct side-effect import is an appropriate fix because it changes build graph visibility without changing renderer runtime behavior.I verified the new behavior test is meaningful by temporarily removing the direct import: with the old transitive helper path stubbed out, webpack emitted the expected
Client runtime at react-on-rails-rsc/client was not foundwarning and the test failed. Restoring the import made the same test pass and emitreact-client-manifest.json. The Pro dummy build also emits bothreact-client-manifest.jsonandreact-server-client-manifest.jsonsuccessfully.Test plan
pnpm --filter react-on-rails-pro exec jest tests/wrapServerComponentRenderer.client.manifest.test.js --runInBandpassesimport 'react-on-rails-rsc/client.browser';makes the new manifest test fail with the upstream missing-client-runtime warningpnpm --filter react-on-rails-pro exec jest tests/wrapServerComponentRenderer.client.manifest.test.js tests/wrapServerComponentRenderer.client.imports.test.ts --runInBandpassespnpm --filter react-on-rails-pro exec jest tests/createReactOnRailsPro.test.ts --runInBandpassespnpm --filter react-on-rails-pro run test:non-rscpasses: 7 suites, 58 testspnpm --filter react-on-rails-pro run type-checkpassespnpm run lintpasses(cd react_on_rails && bundle exec rubocop)passes(cd react_on_rails_pro && bundle exec rubocop --ignore-parent-exclusion)passes; RuboCop prints deprecation warnings from dependencies but reports no offensespnpm exec prettier --check packages/react-on-rails-pro/tests/wrapServerComponentRenderer.client.manifest.test.js packages/react-on-rails-pro/tests/fixtures/rsc-manifest/getReactServerComponent.client.stub.tspassesRAILS_ENV=test NODE_ENV=development bundle exec bin/shakapackerfromreact_on_rails_pro/spec/dummypasses and emitsreact-client-manifest.jsonplusreact-server-client-manifest.jsongit diff --checkpasses3013f9d90pnpm start format.listDifferentis blocked by an existing ignored.context/plans/issue-3366-reproduction-plan.mdformatting issue; the PR files pass direct Prettier checks🤖 Generated with Claude Code
Note
Medium Risk
Touches Pro RSC client entrypoint bundling to force inclusion of the RSC client runtime, which can affect build output and hydration behavior if misconfigured. Added tests reduce regression risk, but changes are in a critical RSC packaging path.
Overview
Restores reliable React Server Components hydration for Pro by adding a top-level side-effect import of
react-on-rails-rsc/client.browserinwrapServerComponentRenderer/client.tsx, ensuringRSCWebpackPluginconsistently emitsreact-client-manifest.jsoneven when transitive imports are disrupted.Adds regression coverage: a structural test that enforces the presence of the side-effect import (source and built
libwhen available) and a webpack-level test that stubsgetReactServerComponent.clientto prove manifest emission still occurs. Updatesknip.tsto ignore dynamically referenced test fixtures and records the fix inCHANGELOG.md.Reviewed by Cursor Bugbot for commit 1bb24cc. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit
Bug Fixes
Tests
Documentation