Skip to content

Commit 8d49ef7

Browse files
committed
Merge remote-tracking branch 'origin/main' into codex/example-migrations-docs
* origin/main: chore: remove redundant --rsc-pro install generator flag (#3105) ci: warn (don't fail) on Bencher main regression (#3168) test: enable RSpec --profile to surface slowest package tests (#3176) fix(node-renderer): expose performance in VM context when supportModules (#3158) docs: remove stale immediate_hydration references (#3139) (#3159) docs: restore absolute URL for node-renderer testing example (#3179) Bump Rspack dependencies to v2 (^2.0.0-0) (#3084) chore: remove obsolete webpack <5.106.0 pin (#3175) Move Node Renderer entry point to renderer/ directory (#3165) docs: address RSC pitfalls review follow-ups (#3155) (#3156) docs: remove fabricated DevConsole reference, link verified RSC tools (#2527) (#3163) # Conflicts: # docs/oss/building-features/node-renderer/js-configuration.md
2 parents 4adf758 + efe3503 commit 8d49ef7

54 files changed

Lines changed: 1055 additions & 548 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/benchmark.yml

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,12 @@ jobs:
157157
# ============================================
158158

159159
- name: Install Bencher CLI
160-
uses: bencherdev/bencher@main
160+
# Pinned: step 7a's alert-detection heuristic greps stderr for
161+
# "Alert[s]" / "threshold violation" / "boundary violation". Those strings
162+
# are not a documented API contract, so upgrading the CLI requires
163+
# re-verifying that the expected phrases still appear in alert output.
164+
# Expected stderr phrases at v0.6.2: "🚨 N Alerts", "Alert detected".
165+
uses: bencherdev/bencher@v0.6.2
161166

162167
- name: Add tools directory to PATH
163168
run: |
@@ -650,11 +655,27 @@ jobs:
650655
echo "::warning::Bencher baseline not found for start-point hash '$START_POINT_HASH' — regression comparison unavailable for this run"
651656
BENCHER_EXIT_CODE=0
652657
fi
653-
rm -f "$BENCHER_STDERR"
654658
655-
# Export exit code early so downstream alerting steps (7b/7c) always see it,
656-
# even if the post-processing below (step summary, PR comments) fails.
659+
# Distinguish regression alerts from operational failures (auth/API/network/CLI)
660+
# so that main can warn-only on the former while still failing hard on the latter.
661+
# Match Bencher's actual alert output (e.g., "🚨 2 Alerts", "Alert detected") via
662+
# a word-bounded, case-insensitive "Alert[s]". The word boundary (\b) already
663+
# prevents matching lowercase "alerts" inside URL paths (e.g., "/v0/.../alerts"),
664+
# so the case-insensitive flag adds robustness against future CLI wording changes
665+
# (e.g., lowercase "1 alert") without reintroducing the URL-path false positive.
666+
BENCHER_HAS_ALERT=0
667+
if [ $BENCHER_EXIT_CODE -ne 0 ] && \
668+
{ grep -qiE "\bAlerts?\b" "$BENCHER_STDERR" || \
669+
grep -qiE "threshold violation|boundary violation" "$BENCHER_STDERR"; }; then
670+
BENCHER_HAS_ALERT=1
671+
fi
672+
# Cleanup is also handled by the `trap 'rm -f "$BENCHER_STDERR"' EXIT` above,
673+
# so we don't need an explicit rm here.
674+
675+
# Export exit code and alert flag early so downstream steps (7b/7c/7d) always
676+
# see them, even if the post-processing below (step summary, PR comments) fails.
657677
echo "BENCHER_EXIT_CODE=$BENCHER_EXIT_CODE" >> "$GITHUB_ENV"
678+
echo "BENCHER_HAS_ALERT=$BENCHER_HAS_ALERT" >> "$GITHUB_ENV"
658679
659680
# Post report to job summary and PR comment(s) if there's HTML output
660681
if [ -s bench_results/bencher_report.html ]; then
@@ -703,7 +724,7 @@ jobs:
703724
# STEP 7b: ALERT ON MAIN REGRESSION
704725
# ============================================
705726
- name: Create GitHub Issue for main regression
706-
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && env.BENCHER_EXIT_CODE != '0'
727+
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && env.BENCHER_EXIT_CODE != '0' && env.BENCHER_HAS_ALERT == '1'
707728
env:
708729
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
709730
run: |
@@ -789,14 +810,35 @@ jobs:
789810
fi
790811
791812
# ============================================
792-
# STEP 7c: FAIL WORKFLOW ON MAIN REGRESSION
813+
# STEP 7c: WARN ON MAIN REGRESSION
814+
# ============================================
815+
# Regressions are surfaced via the Bencher dashboard, the auto-opened tracking
816+
# issue (Step 7b), and the job-summary report. Do not fail the workflow on regression
817+
# alerts: single-run alerts on GitHub-hosted runners are dominated by environmental
818+
# noise (alert sets across consecutive runs have ~0 overlap), so blocking main
819+
# produces churn without signal. Re-enable a hard gate once thresholds/sample-size
820+
# tolerate that noise. Operational errors (auth/API/network/CLI) are handled in
821+
# step 7d and still fail the workflow.
822+
- name: Warn if Bencher detected regression on main
823+
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && env.BENCHER_EXIT_CODE != '0' && env.BENCHER_HAS_ALERT == '1'
824+
env:
825+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
826+
run: |
827+
echo "::warning::Bencher flagged a regression on main (exit ${BENCHER_EXIT_CODE:-1}). See the open regression issue (label: performance-regression), the Bencher dashboard, and the workflow run: ${RUN_URL}"
828+
829+
# ============================================
830+
# STEP 7d: FAIL ON NON-REGRESSION BENCHER ERRORS
793831
# ============================================
794-
# Only fail on main — PR benchmarks are informational (triggered by 'benchmark' label).
795-
# Regressions on PRs are surfaced via Bencher report comments, not workflow failures.
796-
- name: Fail workflow if Bencher detected regression on main
797-
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && env.BENCHER_EXIT_CODE != '0'
832+
# If bencher exited non-zero but stderr did not contain regression alert
833+
# indicators, this is an operational failure (auth/API/network/CLI) rather than
834+
# a performance signal. These should still fail the workflow on main so a broken
835+
# benchmark pipeline (missing uploads, bad credentials, Bencher outage, etc.) is
836+
# not silently hidden behind a warning annotation.
837+
- name: Fail on non-regression Bencher error on main
838+
if: github.event_name == 'push' && github.ref == 'refs/heads/main' && env.BENCHER_EXIT_CODE != '0' && env.BENCHER_HAS_ALERT != '1'
798839
run: |
799-
echo "Bencher detected a regression (exit code: ${BENCHER_EXIT_CODE:-1})"
840+
echo "::error::Bencher exited ${BENCHER_EXIT_CODE:-1} on main with no regression alert in stderr — this indicates an operational failure (auth/API/network/CLI), not a performance regression. Check the logs above."
841+
# Preserve the original Bencher exit code in CI logs for diagnostic context.
800842
exit "${BENCHER_EXIT_CODE:-1}"
801843
802844
# ============================================

.lychee.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ exclude = [
8080
# ============================================================================
8181
'^https://blog\.shakacode\.com', # May block automated requests
8282
'^https?://(www\.)?foxford\.ru', # Russian site, often blocks bots
83+
'^https://(www\.)?guavapass\.com', # TLS handshake fails from GitHub Actions (bot filter)
8384
'^https://(www\.)?airgoat\.com', # Returns 403
8485
'^https://(www\.)?first\.io', # Returns 403
8586
'^https://(www\.)?estately\.com', # Returns 403

CHANGELOG.md

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

2525
### [Unreleased]
2626

27+
#### Removed
28+
29+
- **[Pro]** **Removed the `--rsc-pro` install generator flag**: `--rsc` already implies Pro, so the separate mode was unnecessary. Behaviors previously gated on `--rsc-pro` (Pro verification checklist, prerelease install note, exact Pro gem pin on prereleases) now fire on `--rsc` installs. See also [Issue 3104](https://github.com/shakacode/react_on_rails/issues/3104), which tracks unrelated silent-failure bugs in the Pro upgrade automation. [PR 3105](https://github.com/shakacode/react_on_rails/pull/3105) by [ihabadham](https://github.com/ihabadham).
30+
31+
#### Changed
32+
33+
- **[Pro]** **Pro generator now creates the Node Renderer at `renderer/node-renderer.js`**: The canonical location for the Node Renderer entry point is now a dedicated top-level `renderer/` directory instead of `client/`, making it straightforward to exclude from production Docker builds that strip JS sources after bundling. Docs and Pro `spec/dummy` now use the new path consistently. Existing apps are unaffected — the generator skips files that already exist (including a legacy `client/node-renderer.js`). Fixes [Issue 3073](https://github.com/shakacode/react_on_rails/issues/3073). [PR 3165](https://github.com/shakacode/react_on_rails/pull/3165) by [justin808](https://github.com/justin808).
34+
2735
#### Fixed
2836

37+
- **[Pro]** **Node renderer now exposes `performance` when `supportModules: true`**: React 19's development build of `React.lazy` calls `performance.now()`, which previously threw `ReferenceError: performance is not defined` inside the node renderer's VM context unless users manually added `performance` via `additionalContext`. `performance` is now included in the default globals alongside `Buffer`, `process`, etc. Fixes [Issue 3154](https://github.com/shakacode/react_on_rails/issues/3154). [PR 3158](https://github.com/shakacode/react_on_rails/pull/3158) by [justin808](https://github.com/justin808).
2938
- **Client startup now recovers if initialization begins during `interactive` after `DOMContentLoaded` already fired**: React on Rails now still initializes the page when the client bundle starts in the browser timing window after `DOMContentLoaded` but before the document reaches `complete`. Fixes [Issue 3150](https://github.com/shakacode/react_on_rails/issues/3150). [PR 3151](https://github.com/shakacode/react_on_rails/pull/3151) by [ihabadham](https://github.com/ihabadham).
3039
- **Doctor accepts TypeScript server bundle entrypoints**: `react_on_rails:doctor` now resolves common source entrypoint suffixes (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`) before warning that the server bundle is missing, preventing false positives when apps use `server-bundle.ts`. [PR 3111](https://github.com/shakacode/react_on_rails/pull/3111) by [justin808](https://github.com/justin808).
3140
- **Doctor no longer fails custom projects for a missing generated `bin/dev`**: `react_on_rails:doctor` now downgrades a missing official React on Rails `bin/dev` launcher from an error to a warning and adds explicit guidance when a custom `./dev` script is detected, so custom projects can pass diagnostics when their development setup is intentional. Fixes [Issue 3103](https://github.com/shakacode/react_on_rails/issues/3103). [PR 3117](https://github.com/shakacode/react_on_rails/pull/3117) by [justin808](https://github.com/justin808).
3241

42+
#### Changed
43+
44+
- **Rspack install scaffolding now targets Rspack v2**: `react_on_rails:install --rspack` and `bin/switch-bundler` now generate the Rspack v2 package line (`@rspack/core@^2.0.0-0`, `@rspack/cli@^2.0.0-0`, `@rspack/plugin-react-refresh@^2.0.0`) while keeping `rspack-manifest-plugin@^5.0.0`, which is already compatible. Closes [Issue 3082](https://github.com/shakacode/react_on_rails/issues/3082). [PR 3084](https://github.com/shakacode/react_on_rails/pull/3084) by [justin808](https://github.com/justin808).
45+
3346
### [16.6.0] - 2026-04-09
3447

3548
#### Removed

benchmarks/bench-node-renderer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def read_protocol_version
2323

2424
def read_password_from_config
2525
config_path = File.expand_path(
26-
"../react_on_rails_pro/spec/dummy/client/node-renderer.js",
26+
"../react_on_rails_pro/spec/dummy/renderer/node-renderer.js",
2727
__dir__
2828
)
2929
config_content = File.read(config_path)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ can pass the redux option if you'd like to have redux setup for you automaticall
5050
Passing the --pro generator option sets up React on Rails Pro with Node
5151
server rendering, fragment caching, and code-splitting support.
5252
Requires the react_on_rails_pro gem (add it to your Gemfile first).
53-
Creates the Pro initializer, node-renderer.js, and adds the Node Renderer
53+
Creates the Pro initializer, renderer/node-renderer.js, and adds the Node Renderer
5454
process to Procfile.dev.
5555
5656
* RSC (React Server Components)
@@ -215,7 +215,7 @@ rails generate react_on_rails:install --pro
215215
**What gets created:**
216216

217217
- `config/initializers/react_on_rails_pro.rb` - Pro configuration with Node Renderer settings
218-
- `client/node-renderer.js` - Node Renderer bootstrap file
218+
- `renderer/node-renderer.js` - Node Renderer bootstrap file
219219
- Node Renderer process added to `Procfile.dev`
220220
- Pro npm packages (`react-on-rails-pro`, `react-on-rails-pro-node-renderer`)
221221

docs/oss/api-reference/ruby-api-pro.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ Streams a server-rendered React component using React 18's `renderToPipeableStre
4747

4848
Requires the controller to use `stream_view_containing_react_components`.
4949

50-
Options: same as `react_component` plus:
50+
Options: same as `react_component`.
5151

52-
- `:immediate_hydration` (default: `true`) — controls hydration timing
52+
> **Note (Pro):** React on Rails Pro hydrates streamed components early (before `DOMContentLoaded`) automatically — no per-component toggle is exposed.
5353
5454
```ruby
5555
<%= stream_react_component("App", props: { data: @data }) %>

docs/oss/building-features/code-splitting.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ in your webpack configuration. That turns off the polyfills for things like `__d
252252
253253
### Node Renderer
254254
255-
In your `node-renderer.js` file which runs node renderer, you need to specify `supportModules` options as follows:
255+
In your `renderer/node-renderer.js` file which runs node renderer, you need to specify `supportModules` options as follows:
256256
257257
```js
258258
const path = require('path');

docs/oss/building-features/node-renderer/basics.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ See the [Memory Leaks guide](../../../pro/js-memory-leaks.md) for common leak pa
2727

2828
**node-renderer** is a standalone Node application to serve React SSR requests from a **Rails** client. You don't need any **Ruby** code to setup and launch it. You can configure with the command line or with a launch file.
2929

30-
> **Generator shortcut:** Running `rails generate react_on_rails:install --pro` (or `rails generate react_on_rails:pro` for existing apps) automatically creates `client/node-renderer.js`, adds the Node Renderer process to `Procfile.dev`, and installs the required npm packages. See [Installation](../../../pro/installation.md) for details. The manual setup below is for apps that need custom configuration.
30+
> **Generator shortcut:** Running `rails generate react_on_rails:install --pro` (or `rails generate react_on_rails:pro` for existing apps) automatically creates `renderer/node-renderer.js`, adds the Node Renderer process to `Procfile.dev`, and installs the required npm packages. See [Installation](../../../pro/installation.md) for details. The manual setup below is for apps that need custom configuration.
3131
3232
## Simple Command Line for node-renderer
3333

@@ -65,7 +65,7 @@ For the most control over the setup, create a JavaScript file to start the NodeR
6565
# or: yarn add react-on-rails-pro-node-renderer
6666
# or: bun add react-on-rails-pro-node-renderer
6767
```
68-
4. Configure a JavaScript file that will launch the rendering server per the docs in [Node Renderer JavaScript Configuration](./js-configuration.md). For example, create a file `node-renderer.js`. Here is a simple example that uses all the defaults except for serverBundleCachePath:
68+
4. Configure a JavaScript file that will launch the rendering server per the docs in [Node Renderer JavaScript Configuration](./js-configuration.md). For example, create a file `renderer/node-renderer.js`. Here is a simple example that uses all the defaults except for serverBundleCachePath:
6969

7070
```javascript
7171
import path from 'path';
@@ -78,7 +78,7 @@ For the most control over the setup, create a JavaScript file to start the NodeR
7878
reactOnRailsProNodeRenderer(config);
7979
```
8080

81-
5. Now you can launch your renderer server with `node node-renderer.js`. You will probably add a script to your `package.json`.
81+
5. Now you can launch your renderer server with `node renderer/node-renderer.js`. You will probably add a script to your `package.json`.
8282
6. You can use a command line argument of `-p SOME_PORT` to override any configured or ENV value for the port.
8383

8484
## Setup Rails Application
@@ -157,13 +157,14 @@ The Node Renderer must be started as a background process before running tests.
157157
# .github/workflows/test.yml (GitHub Actions example)
158158
jobs:
159159
test:
160+
runs-on: ubuntu-latest
160161
env:
161162
# Job-level: both the renderer and Rails test steps need this
162163
RENDERER_PASSWORD: ${{ secrets.RENDERER_PASSWORD }}
163164
steps:
164165
- name: Start Node Renderer
165166
run: |
166-
node client/node-renderer.js &
167+
node renderer/node-renderer.js &
167168
# Wait for the renderer to be ready.
168169
# The renderer uses cleartext HTTP/2 (h2c), so use --http2-prior-knowledge for the probe.
169170
# --max-time 2 prevents hangs if the port is open but the process is stalled.

docs/oss/building-features/node-renderer/container-deployment.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ end
131131
132132
## Dockerfile Example
133133

134+
> **Why the renderer entry point lives in a dedicated `renderer/` directory:** Production Docker builds commonly strip JavaScript sources after the client bundles are built, since the Rails app no longer needs them at runtime. Keeping the renderer entry point in its own top-level directory (separate from `client/`) makes it trivial to exclude from that cleanup — the Node Renderer process still needs its entry file and dependencies at runtime.
135+
134136
A minimal Dockerfile that bundles Rails and the Node Renderer in a single image:
135137

136138
```dockerfile
@@ -175,10 +177,10 @@ For the single-container pattern, use a process manager like [overmind](https://
175177
```text
176178
# Procfile
177179
rails: bundle exec rails server -b 0.0.0.0 -p 3000
178-
renderer: node client/node-renderer.js
180+
renderer: node renderer/node-renderer.js
179181
```
180182

181-
> **Tip:** For sidecar containers, use the same image but override the `CMD` — one container runs `bundle exec rails server`, the other runs `node client/node-renderer.js` (or your Node Renderer entry point).
183+
> **Tip:** For sidecar containers, use the same image but override the `CMD` — one container runs `bundle exec rails server`, the other runs `node renderer/node-renderer.js` (or your Node Renderer entry point).
182184
183185
## Docker Compose Example
184186

@@ -199,7 +201,7 @@ services:
199201

200202
renderer:
201203
build: .
202-
command: node client/node-renderer.js
204+
command: node renderer/node-renderer.js
203205
ports:
204206
- '3800:3800'
205207
environment:
@@ -220,7 +222,7 @@ services:
220222
By default, the Node Renderer binds to `localhost`. For **sidecar containers** in the same Kubernetes pod, that works because the containers share a network namespace. For **separate workloads** or Docker Compose setups without shared networking, bind to `0.0.0.0`:
221223

222224
```javascript
223-
// node-renderer.js
225+
// renderer/node-renderer.js
224226
import { reactOnRailsProNodeRenderer } from 'react-on-rails-pro-node-renderer';
225227
226228
const config = {
@@ -488,7 +490,7 @@ spec:
488490

489491
- name: node-renderer
490492
image: your-app:latest # Same image as Rails
491-
command: ['node', 'client/node-renderer.js']
493+
command: ['node', 'renderer/node-renderer.js']
492494
ports:
493495
- containerPort: 3800
494496
env:

docs/oss/building-features/node-renderer/debugging.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Use Node's built-in flag to write heap snapshots on demand:
6363
```bash
6464
cd react_on_rails_pro/spec/dummy
6565
# Adjust the port if your Rails app points at a different renderer URL.
66-
NODE_OPTIONS="--heapsnapshot-signal=SIGUSR2" RENDERER_PORT=3800 node client/node-renderer.js
66+
NODE_OPTIONS="--heapsnapshot-signal=SIGUSR2" RENDERER_PORT=3800 node renderer/node-renderer.js
6767
```
6868

6969
Then capture snapshots at different times:

0 commit comments

Comments
 (0)