Skip to content

Commit c2de1e1

Browse files
authored
Reduce Rspack RSC dev-server manifest footgun (#4234)
## Summary Follow-up to #4227 / #4226. The original fix disabled Rspack lazy compilation for generated RSC apps, but the behavior was still easy for existing apps to miss and one helper still assumed the RSC config lived under `config/webpack`. This PR: - disables Rspack lazy compilation in generated dev-server config when an RSC config is present - makes RSC client-reference discovery check the sibling `rscWebpackConfig.js` in the active config directory - adds `react_on_rails:doctor:rsc` diagnostics for existing Rspack RSC apps whose dev config does not disable lazy compilation - documents the empty React Client Manifest / `lazy-compilation-proxy` / `POST /_rspack/lazy/trigger` 404 troubleshooting path - adds the #4234 changelog entry under Unreleased / Fixed ## Validation - `cd react_on_rails && bundle exec rspec spec/react_on_rails/generators/install_generator_spec.rb -e 'disables Rspack lazy compilation while serving RSC apps' -e 'checks RSC discovery support in the generated Rspack config directory'` - `cd react_on_rails && bundle exec rspec spec/react_on_rails/generators/rsc_generator_spec.rb -e 'when Pro is installed on an existing rspack project'` - `cd react_on_rails && bundle exec rspec spec/react_on_rails/generators/rsc_generator_spec.rb -e 'when an existing client RSC webpack config already references the scoped helper'` - `cd react_on_rails && bundle exec rspec spec/lib/react_on_rails/doctor_spec.rb -e 'check_rsc_rspack_lazy_compilation' -e 'warns when the Rspack RSC config supports manifest discovery and the manifest is missing'` - `bundle exec rubocop react_on_rails/lib/react_on_rails/doctor.rb react_on_rails/lib/generators/react_on_rails/rsc_setup/client_references.rb react_on_rails/spec/lib/react_on_rails/doctor_spec.rb react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb react_on_rails/spec/react_on_rails/generators/rsc_generator_spec.rb` - `pnpm start format.listDifferent` - `git diff --check origin/main...HEAD` - `script/ci-changes-detector origin/main` - `/Users/justin/.agents/skills/autoreview/scripts/autoreview --mode local` clean before push - pre-push hook: branch RuboCop plus online Markdown link check passed <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved RSC dev-server diagnostics for Rspack, with warnings when client lazy compilation isn’t disabled and it may cause an empty React Client Manifest. - Updated diagnostics to verify client-reference discovery using the correct RSC config location. - Refined RSC + Rspack setup guidance to ensure the Rspack development config disables client lazy compilation when RSC is present. - **Documentation** - Clarified supported locations for RSC bundler configuration for both webpack and Rspack. - Expanded RSC troubleshooting with Rspack-specific “empty client manifest” symptoms and remediation. - **Tests** - Updated generator and doctor specs for the revised Rspack/RSC behavior and diagnostics. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 84f1350 commit c2de1e1

11 files changed

Lines changed: 260 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ After a release, run `/update-changelog` in Claude Code to analyze commits, writ
4848

4949
#### Fixed
5050

51+
- **[Pro]** **Rspack RSC dev-server setup is easier to diagnose and customize**:
52+
Generated RSC helper code now verifies client-reference discovery support
53+
through the sibling `rscWebpackConfig.js` file instead of assuming
54+
`config/webpack`, so Rspack apps keep using `config/rspack/rscWebpackConfig.js`.
55+
The RSC doctor also warns existing Rspack apps when normal `bin/dev` can leave
56+
the React Client Manifest empty because `lazyCompilation` is still enabled,
57+
and the troubleshooting guide documents the `lazy-compilation-proxy` /
58+
`POST /_rspack/lazy/trigger` 404 symptom path. Follow-up to
59+
[PR 4227](https://github.com/shakacode/react_on_rails/pull/4227).
60+
[PR 4234](https://github.com/shakacode/react_on_rails/pull/4234) by
61+
[justin808](https://github.com/justin808).
62+
5163
- **Precompile hook UTF-8 hardening now handles conflicting `RUBYOPT` encoding flags**:
5264
The shared Shakapacker precompile hook strips pre-existing Ruby encoding flags such as
5365
`-EUS-ASCII`, `--encoding=US-ASCII`, `--external-encoding=US-ASCII`, and

docs/oss/api-reference/generator-details.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ RSC builds on React on Rails Pro's Node rendering infrastructure. The generator
286286

287287
In addition to all Pro files:
288288

289-
- `config/webpack/rscWebpackConfig.js` - RSC-specific webpack configuration
289+
- `config/webpack/rscWebpackConfig.js` or `config/rspack/rscWebpackConfig.js` - RSC-specific bundler configuration
290290
- `app/javascript/src/HelloServer/` - Example RSC component (replaces HelloWorld)
291291
- `app/controllers/hello_server_controller.rb` - Controller with streaming support
292292
- `app/views/hello_server/index.html.erb` - View using `stream_react_component`

docs/oss/migrating/rsc-troubleshooting.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ When something goes wrong during RSC migration, start here. This table maps symp
2020
| `ReferenceError: fetch is not defined` (or `Headers`, `Request`, `Response`, `AbortController`, `AbortSignal`) | Node renderer VM context missing fetch globals | [Node Renderer VM Context](#node-renderer-vm-context----missing-globals) |
2121
| `ReferenceError: require is not defined` | Server bundle or RSC bundle uses `externals` for Node builtins instead of `resolve.fallback` | [Handling Node Builtins](#handling-node-builtins----externals-vs-resolvefallback) |
2222
| `ReferenceError: MessageChannel is not defined` | `react-dom/server.browser` needs `MessageChannel` at load time in VM sandbox | [MessageChannel Not Defined](#messagechannel-not-defined) |
23+
| RSC says a module is missing from the React Client Manifest and the manifest is empty in Rspack `bin/dev` | Rspack lazy compilation deferred RSC client references before the server renderer read them | [Empty Client Manifest with Rspack Dev Server](#empty-client-manifest-with-rspack-dev-server) |
2324
| SSR hangs or times out on large pages | Stream backpressure deadlock | [Stream Backpressure Deadlock](#stream-backpressure-deadlock) |
2425
| Rails boot error about version mismatch | Gem and npm package at different versions | [Gem and npm Package Version Mismatch](#gem-and-npm-package-version-mismatch) |
2526
| 422 Unprocessable Entity on form submission | Missing CSRF token in fetch request | [Mutations](rsc-data-fetching.md#mutations-rails-controllers-not-server-actions) |
@@ -774,6 +775,54 @@ class Api::UsersController < ApplicationController
774775
end
775776
```
776777

778+
## Empty Client Manifest with Rspack Dev Server
779+
780+
In RSC apps that use Rspack, normal `bin/dev` dev-server mode must compile RSC client references before the server renderer reads the React Client Manifest. If Rspack lazy compilation is still enabled, the first server render can see an empty manifest and fail with an error like:
781+
782+
```text
783+
Could not find the module ".../LikeButton.jsx#default" in the React Client Manifest.
784+
```
785+
786+
Common signs:
787+
788+
- `react-client-manifest.json` contains `"filePathToModuleMetadata": {}`
789+
- the generated Rspack client bundle mentions `lazy-compilation-proxy`
790+
- the browser sends `POST /_rspack/lazy/trigger` and Rails returns `404`
791+
- `bin/dev static` works, but normal `bin/dev` fails
792+
793+
Run the RSC doctor first:
794+
795+
```bash
796+
bundle exec rails react_on_rails:doctor:rsc
797+
```
798+
799+
For existing generated apps, update `config/rspack/development.js` so the Rspack dev-server branch disables lazy compilation when an RSC config is present:
800+
801+
```js
802+
const developmentEnvOnly = (clientWebpackConfig, _serverWebpackConfig, rscWebpackConfig) => {
803+
if (process.env.WEBPACK_SERVE) {
804+
if (config.assets_bundler === 'rspack') {
805+
const { ReactRefreshRspackPlugin } = require('@rspack/plugin-react-refresh');
806+
clientWebpackConfig.plugins.push(new ReactRefreshRspackPlugin());
807+
808+
if (rscWebpackConfig) {
809+
clientWebpackConfig.lazyCompilation = false;
810+
}
811+
}
812+
}
813+
};
814+
```
815+
816+
Also verify that `config/rspack/ServerClientOrBoth.js` passes the RSC config as the third argument to the environment-specific config hook:
817+
818+
```js
819+
envSpecific(clientConfig, serverConfig, rscConfig);
820+
```
821+
822+
If it only passes `clientConfig` and `serverConfig`, rerun the RSC generator to refresh both `ServerClientOrBoth.js` and `development.js`.
823+
824+
Fresh generator output includes this guard. If your config has been heavily customized, keep the important behavior: RSC + Rspack + dev-server mode should set `clientWebpackConfig.lazyCompilation = false`.
825+
777826
## Bundle Analysis Tools
778827

779828
| Tool | Purpose |
@@ -810,6 +859,7 @@ end
810859
| `ReferenceError: require is not defined` | Server or RSC bundle webpack config uses `externals` for Node builtins, generating `require()` calls that fail in VM sandbox | Use `resolve.fallback: { path: false, fs: false }` instead of `externals`. See [Handling Node Builtins](#handling-node-builtins----externals-vs-resolvefallback) |
811860
| `ReferenceError: MessageChannel is not defined` | `react-dom/server.browser` instantiates `MessageChannel` at module load time; VM sandbox lacks this global | Inject a `BannerPlugin` polyfill in the server webpack config. See [MessageChannel Not Defined](#messagechannel-not-defined) |
812861
| `"global object mismatch"` | `react-on-rails` and `react-on-rails-pro` resolved from different sources (e.g., npm vs yalc) | Force consistent resolution with `pnpm.overrides` or `yarn.resolutions`. See [Version Mismatch](#version-mismatch----global-object-mismatch) |
862+
| Missing module in React Client Manifest with empty `filePathToModuleMetadata` in Rspack `bin/dev` | Rspack lazy compilation deferred RSC client references before manifest generation | Disable Rspack lazy compilation for RSC dev-server mode. See [Empty Client Manifest with Rspack Dev Server](#empty-client-manifest-with-rspack-dev-server) |
813863
| SSR hangs indefinitely / request timeout on large RSC payloads | Stream backpressure deadlock when RSC payload exceeds 16 KB | Update to latest React on Rails Pro. See [Stream Backpressure Deadlock](#stream-backpressure-deadlock) |
814864
| `"The 'react-on-rails' package version does not match the gem version"` | Gem and npm package installed at different versions | Install the npm package version matching your gem. See [Gem and npm Package Version Mismatch](#gem-and-npm-package-version-mismatch) |
815865
| `"The 'react-on-rails' package version is not an exact version"` | Using semver ranges (`^`, `~`, `*`) instead of an exact version in package.json | Pin to the exact version without range operators. See [Gem and npm Package Version Mismatch](#gem-and-npm-package-version-mismatch) |

docs/pro/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ bundle exec rails generate react_on_rails:install --rsc
9191
bundle exec rails generate react_on_rails:rsc
9292
```
9393

94-
The RSC generator creates `rscWebpackConfig.js`, adds `RSCWebpackPlugin` to both server and client webpack configs, configures `RSC_BUNDLE_ONLY` handling in `ServerClientOrBoth.js`, and sets up the RSC bundle watcher process. See [React Server Components](./react-server-components/tutorial.md) for more information.
94+
The RSC generator creates `rscWebpackConfig.js` in the active bundler config directory (`config/webpack/` or `config/rspack/`), adds the RSC bundler plugin to both server and client configs, configures `RSC_BUNDLE_ONLY` handling in `ServerClientOrBoth.js`, and sets up the RSC bundle watcher process. See [React Server Components](./react-server-components/tutorial.md) for more information.
9595

9696
---
9797

react_on_rails/lib/generators/react_on_rails/rsc/USAGE

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ Example:
66
rails generate react_on_rails:rsc --typescript
77

88
This will add:
9-
- RSC webpack configuration (config/webpack/rscWebpackConfig.js)
9+
- RSC bundler configuration (config/webpack/rscWebpackConfig.js or config/rspack/rscWebpackConfig.js)
1010
- HelloServer example component
1111
- HelloServerController and view
1212
- RSC routes (rsc_payload_route, hello_server)
1313
- RSC bundle watcher to Procfile.dev
1414

1515
Modifies:
16-
- config/webpack/serverWebpackConfig.js (adds RSCWebpackPlugin, rscBundle param)
17-
- config/webpack/clientWebpackConfig.js (adds RSCWebpackPlugin)
18-
- config/webpack/ServerClientOrBoth.js (adds rscWebpackConfig, RSC_BUNDLE_ONLY)
16+
- config/webpack/serverWebpackConfig.js or config/rspack/serverWebpackConfig.js (adds RSC plugin, rscBundle param)
17+
- config/webpack/clientWebpackConfig.js or config/rspack/clientWebpackConfig.js (adds RSC plugin)
18+
- config/webpack/ServerClientOrBoth.js or config/rspack/ServerClientOrBoth.js (adds rscWebpackConfig, RSC_BUNDLE_ONLY)
1919
- config/initializers/react_on_rails_pro.rb (adds RSC config block)
2020
- package.json (adds react-on-rails-rsc)
2121

react_on_rails/lib/generators/react_on_rails/rsc_setup/client_references.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def rsc_client_references_js
156156
};
157157
158158
const rscConfigSupportsDiscovery = () => {
159-
const rscWebpackConfig = resolve('config/webpack/rscWebpackConfig.js');
159+
const rscWebpackConfig = resolve(__dirname, 'rscWebpackConfig.js');
160160
const precompileHook = resolve('bin/shakapacker-precompile-hook');
161161
162162
return (

react_on_rails/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
<%= add_documentation_reference(config[:message], "// https://github.com/shakacode/react-on-rails-demo-ssr-hmr/blob/master/config/webpack/development.js") %>
22

33
const { devServer, inliningCss, config } = require('shakapacker');
4-
const { existsSync } = require('fs');
5-
const { resolve } = require('path');
64

75
const serverClientOrBoth = require('./ServerClientOrBoth');
86

9-
const developmentEnvOnly = (clientWebpackConfig, _serverWebpackConfig) => {
7+
const developmentEnvOnly = (clientWebpackConfig, _serverWebpackConfig, rscWebpackConfig) => {
108
// React Refresh (Fast Refresh) setup - only when dev server is running (HMR mode)
119
if (process.env.WEBPACK_SERVE) {
1210
// eslint-disable-next-line global-require
@@ -15,7 +13,7 @@ const developmentEnvOnly = (clientWebpackConfig, _serverWebpackConfig) => {
1513
const { ReactRefreshRspackPlugin } = require('@rspack/plugin-react-refresh');
1614
clientWebpackConfig.plugins.push(new ReactRefreshRspackPlugin());
1715

18-
if (existsSync(resolve(__dirname, 'rscWebpackConfig.js'))) {
16+
if (rscWebpackConfig) {
1917
// RSC client references must be compiled before the server render reads the client manifest.
2018
clientWebpackConfig.lazyCompilation = false;
2119
}

react_on_rails/lib/react_on_rails/doctor.rb

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3521,6 +3521,11 @@ def base_package_reference?(content)
35213521
# Keep in sync with RSC_CLIENT_MANIFEST_GUIDANCE in
35223522
# packages/react-on-rails-pro/src/handleErrorRSC.ts.
35233523
RSC_CLIENT_MANIFEST_CLEANUP_PATHS = %w[public/packs public/packs-test ssr-generated .node-renderer-bundles].freeze
3524+
RSC_RSPACK_CONFIG_FILENAME = "rscWebpackConfig.js"
3525+
RSC_RSPACK_DEVELOPMENT_CONFIG_FILENAME = "development.js"
3526+
RSC_RSPACK_CLIENT_LAZY_COMPILATION_DISABLED_PATTERN = /
3527+
^\s*clientWebpackConfig\.lazyCompilation\s*=\s*false\b
3528+
/x
35243529

35253530
def check_rsc_setup
35263531
unless ReactOnRails::Utils.react_on_rails_pro?
@@ -3543,6 +3548,7 @@ def check_rsc_setup
35433548
check_rsc_renderer_mode(pro_config)
35443549
check_rsc_payload_route
35453550
check_rsc_bundler_config
3551+
check_rsc_rspack_lazy_compilation
35463552
check_rsc_react_version
35473553
check_rsc_procfile_watcher
35483554
check_rsc_client_manifest
@@ -3615,6 +3621,49 @@ def check_rsc_bundler_config
36153621
end
36163622
end
36173623

3624+
def check_rsc_rspack_lazy_compilation
3625+
return unless active_assets_bundler == "rspack"
3626+
3627+
config_directory = rsc_rspack_config_directory
3628+
return unless config_directory
3629+
3630+
rsc_config_path = File.join(config_directory, RSC_RSPACK_CONFIG_FILENAME)
3631+
return unless File.exist?(rsc_config_path)
3632+
3633+
development_config_path = File.join(config_directory, RSC_RSPACK_DEVELOPMENT_CONFIG_FILENAME)
3634+
unless File.exist?(development_config_path)
3635+
checker.add_warning("⚠️ RSC Rspack development config not found: #{development_config_path}")
3636+
checker.add_info(" 💡 Cannot verify that Rspack lazyCompilation is disabled for RSC dev-server mode")
3637+
return
3638+
end
3639+
3640+
development_config = File.read(development_config_path)
3641+
if development_config.match?(RSC_RSPACK_CLIENT_LAZY_COMPILATION_DISABLED_PATTERN)
3642+
checker.add_success("✅ Rspack lazy compilation appears disabled for RSC dev-server")
3643+
else
3644+
checker.add_warning(<<~MSG.strip)
3645+
⚠️ Rspack lazyCompilation can leave the RSC client manifest empty in normal bin/dev mode.
3646+
3647+
#{development_config_path} does not appear to disable lazyCompilation while the dev server is running.
3648+
MSG
3649+
checker.add_info(" 💡 Add this inside the Rspack WEBPACK_SERVE branch:")
3650+
checker.add_info(" clientWebpackConfig.lazyCompilation = false;")
3651+
checker.add_info(
3652+
" 💡 See docs/oss/migrating/rsc-troubleshooting.md#empty-client-manifest-with-rspack-dev-server"
3653+
)
3654+
end
3655+
rescue StandardError => e
3656+
checker.add_warning("⚠️ Could not inspect RSC Rspack lazyCompilation config: #{e.message}")
3657+
end
3658+
3659+
def rsc_rspack_config_directory
3660+
explicit_config_path = shakapacker_assets_bundler_config_path
3661+
return bundler_config_directory(explicit_config_path) if explicit_config_path && File.file?(explicit_config_path)
3662+
3663+
default_config_path = ConfigPathResolver::RSPACK_DEFAULT_CONFIG_CANDIDATES.find { |path| File.file?(path) }
3664+
bundler_config_directory(default_config_path)
3665+
end
3666+
36183667
def check_rsc_react_version
36193668
react_version = detect_react_version_from_deps
36203669
unless react_version

0 commit comments

Comments
 (0)