Add REACT_ON_RAILS_PRERENDER_OVERRIDE global prerender override#2816
Add REACT_ON_RAILS_PRERENDER_OVERRIDE global prerender override#2816
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:
WalkthroughAdded a Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 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 unit tests (beta)
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 |
ReviewThe feature is well-scoped and the intent is clear. Three issues worth addressing before merge: Must-fixWarning floods logs on every render (see inline comment on Should-fix
Missing CHANGELOG entry NitThe invalid-value test sets up the |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2171ac2aac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Greptile SummaryThis PR introduces a Key changes:
Minor concerns:
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["#prerender called"] --> B["prerender_env_override"]
B --> C{"REACT_ON_RAILS_PRERENDER_OVERRIDE\nenv var set?"}
C -- "No (nil)" --> D["return nil"]
D --> E["retrieve_configuration_value_for(:prerender)"]
E --> F{"options[:prerender]\nexplicitly set?"}
F -- "Yes" --> G["Return component-level option"]
F -- "No" --> H["Return ReactOnRails.configuration.prerender"]
C -- "Yes" --> I{"Value is\n'true' or 'false'?"}
I -- "Yes (case-insensitive)" --> J["Return true / false"]
I -- "No (invalid)" --> K["Rails.logger.warn\nIgnoring ..."]
K --> D
Reviews (1): Last reviewed commit: "Document prerender override in changelog" | Re-trigger Greptile |
Review SummaryThe feature is well-scoped and useful for CI/test environments. The implementation is clean and the documentation is clear. One meaningful issue and a couple of minor notes: Warning: Missing memoization causes warning spam (real bug)
Minor notes
Looks good
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@analysis/open-issue-deep-dive/wave-2-backlog.md`:
- Line 165: The inline code in the Problem context currently includes the
language identifier "javascript" as part of the snippet; locate the text
fragment that reads `javascript await new Promise(resolve => setTimeout(resolve,
5000));` (context mentions the run-skipped-ci.yml workflow) and remove the
leading "javascript " so the inline code becomes `await new Promise(resolve =>
setTimeout(resolve, 5000));`, preserving surrounding punctuation and formatting.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 41012e59-bc20-43d8-bc33-3da9b1dd88fa
📒 Files selected for processing (4)
analysis/open-issue-deep-dive/README.mdanalysis/open-issue-deep-dive/wave-1-active.mdanalysis/open-issue-deep-dive/wave-2-backlog.mdanalysis/open-issue-deep-dive/wave-3-long-tail.md
✅ Files skipped from review due to trivial changes (3)
- analysis/open-issue-deep-dive/README.md
- analysis/open-issue-deep-dive/wave-1-active.md
- analysis/open-issue-deep-dive/wave-3-long-tail.md
Code ReviewThe implementation is clean and the precedence logic is correct. A few issues worth addressing. Significant: Warning fires on every component render
Suggested fixes: (1) warn once at startup via a class-level flag, or (2) memoize the parsed result on the instance since the env var will not change mid-request. Minor: Global config mutation not cleaned up in test In the invalid-value fallback test, Minor: Missing explicit "env var absent" test in the new describe block The new Nit: Unrelated analysis/open-issue-deep-dive/ changes The wave-1/2/3 markdown edits appear to be tooling churn unrelated to this feature. Consider dropping them to keep the diff focused. |
f3c9601 to
d220132
Compare
Review SummaryThe feature is well-scoped and the implementation is clean. Two issues worth addressing before merging: 1. Warning flood in production (see inline comment on render_options.rb) prerender_env_override is called on every #prerender invocation. With an invalid env value, Rails.logger.warn fires on every component render, rapidly flooding production logs. The warning should fire only once per process — e.g. via a class-level flag set after the first warn. 2. Missing test coverage (see inline comment on spec) None of the three new specs tests the env override when no explicit prerender: option is given at the component level — i.e. env overriding the config default rather than an explicit component option. Should be covered. Minor observations
Overall a useful escape hatch for CI/test environments. The log flooding issue is the main concern. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Test helper clears ENV at wrong lifecycle phase
- I updated the helper to optionally execute assertions inside the cleared-env block and changed the configurable-options specs to run their expectations within that block for
:prerender.
- I updated the helper to optionally execute assertions inside the cleared-env block and changed the configurable-options specs to run their expectations within that block for
Or push these changes by commenting:
@cursor push 4b30b23623
Preview (4b30b23623)
diff --git a/react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb b/react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb
--- a/react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb
+++ b/react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb
@@ -38,10 +38,16 @@
def build_render_options_with_clean_prerender_env(option:, attrs:)
if option == :prerender
with_prerender_env_override_cleared do
- described_class.new(**attrs)
+ render_options = described_class.new(**attrs)
+ return yield(render_options) if block_given?
+
+ render_options
end
else
- described_class.new(**attrs)
+ render_options = described_class.new(**attrs)
+ return yield(render_options) if block_given?
+
+ render_options
end
end
@@ -214,9 +220,9 @@
options = {}
options[option] = false
attrs = the_attrs(options: options)
- opts = build_render_options_with_clean_prerender_env(option: option, attrs: attrs)
-
- expect(opts.public_send(option)).to be false
+ build_render_options_with_clean_prerender_env(option: option, attrs: attrs) do |opts|
+ expect(opts.public_send(option)).to be false
+ end
end
end
@@ -224,9 +230,9 @@
it "returns #{option} from config" do
ReactOnRails.configuration.public_send(:"#{option}=", true)
attrs = the_attrs
- opts = build_render_options_with_clean_prerender_env(option: option, attrs: attrs)
-
- expect(opts.public_send(option)).to be true
+ build_render_options_with_clean_prerender_env(option: option, attrs: attrs) do |opts|
+ expect(opts.public_send(option)).to be true
+ end
end
end
end
PR Review: Add REACT_ON_RAILS_PRERENDER_OVERRIDE global prerender overrideThe feature direction is sound and fills a real gap (disabling SSR in CI/test environments). The implementation is mostly clean, but there are a few concerns worth addressing before merge. Thread Safety (High)
Unnecessary Caching Complexity (Medium) The caching exists mainly to emit the invalid-value warning only once. A simpler alternative: parse the env var once at class-load time, or just let the cheap parse run on every call. The current design leaks class-level mutable state across test examples and couples tests to caching behavior (see below). Test: The
The helper restores the ENV variable correctly but leaves
This helper branches on the option name to decide whether to clear the env. A single top-level Overall the feature is well-scoped and the test coverage is appreciated. Thread safety and test brittleness are the main things to resolve before merge. |
|
Addressed the blocking review items in latest push:\n\n- Added class-level caching of parsing so invalid values no longer warn on every render path.\n- Kept cache keyed by raw env value so changed env values are re-parsed, while stable values avoid repeated warning/log overhead.\n- Scoped env cleanup into helper usage + block, and added explicit config restore in to avoid global-state leakage.\n- Updated invalid-value spec to assert warning fires once and added explicit no-env-override precedence coverage.\n\nLocal verification:\n- Inspecting 2 files 2 files inspected, no offenses detected\n- Run options: include {focus: true} All examples were filtered out; ignoring {focus: true} Finished in 0.00869 seconds (files took 0.82004 seconds to load) Run options: --seed 1121 Running:Finished in 0.000455s, 0.0000 runs/s, 0.0000 assertions/s. 0 runs, 0 assertions, 0 failures, 0 errors, 0 skips |
| Rails.logger.warn( | ||
| "[REACT ON RAILS] Ignoring #{PRERENDER_OVERRIDE_ENV_KEY}=#{raw_value.inspect}. " \ | ||
| "Expected 'true' or 'false'." | ||
| ) |
There was a problem hiding this comment.
Logging inside a mutex
Rails.logger.warn is called while holding PRERENDER_OVERRIDE_CACHE_MUTEX. Rails loggers are typically thread-safe but can involve I/O, synchronization, or custom log subscribers. Calling non-trivial code inside a mutex is a deadlock risk if the logger ever tries to acquire another lock that also waits on this one.
Move the warning outside the mutex:
def prerender_env_override
value, should_warn = PRERENDER_OVERRIDE_CACHE_MUTEX.synchronize do
raw_value = ENV.fetch(PRERENDER_OVERRIDE_ENV_KEY, nil)
cached = @prerender_env_override_cache
next [cached[:value], false] if cached && cached[:raw_value] == raw_value
parsed, warn = parse_prerender_env_override(raw_value)
@prerender_env_override_cache = { raw_value: raw_value, value: parsed }
[parsed, warn]
end
if should_warn
Rails.logger.warn("[REACT ON RAILS] Ignoring ...")
end
value
end| it "overrides explicit option when env is true" do | ||
| ENV["REACT_ON_RAILS_PRERENDER_OVERRIDE"] = "true" | ||
| attrs = the_attrs(options: { prerender: false }) | ||
| opts = described_class.new(**attrs) | ||
|
|
||
| expect(opts.prerender).to be true | ||
| end |
There was a problem hiding this comment.
Missing cache reset after setting ENV in tests
After setting ENV["REACT_ON_RAILS_PRERENDER_OVERRIDE"] = "true", no reset_prerender_env_override_cache! is called. The test works correctly because the around block's with_prerender_env_override_cleared resets the cache before the example runs. The cache is nil at this point, so the first opts.prerender call will correctly re-read ENV.
However, this relies on a subtle invariant: the cache must be nil (not populated with a stale nil entry from some other value) before setting the env var. This is fragile if the test order changes or if tests are added that populate the cache with nil before this block.
For clarity and robustness, add a reset_prerender_env_override_cache! call after setting the env var:
it "overrides explicit option when env is true" do
ENV["REACT_ON_RAILS_PRERENDER_OVERRIDE"] = "true"
described_class.reset_prerender_env_override_cache!
# ...
endThis pattern should be applied consistently across all tests in this describe block that set the env var directly.
Review SummaryThe feature itself is well-conceived — a global However, there are a few implementation concerns worth addressing before merging: Critical: Mutex on the render hot path
Medium: Logger called inside mutex
Minor:
|
e8b1675 to
daa8421
Compare
| - **Global prerender env override**: Added `REACT_ON_RAILS_PRERENDER_OVERRIDE=true|false` to force prerender behavior | ||
| globally (env > component option > initializer default), useful for CI/test environments without an SSR server. | ||
| [PR 2816](https://github.com/shakacode/react_on_rails/pull/2816) by | ||
| [justin808](https://github.com/justin808). |
There was a problem hiding this comment.
The new entry was inserted in the wrong position, breaking PR 2818's attribution. Currently the rendered output reads:
[PR 2818](…) by
- **Global prerender env override**: …
[PR 2816](…) by
[justin808](…).
PR 2818 loses its author attribution and the [justin808] link only belongs to PR 2816. The two entries need to be separate, each with their own by [author] suffix.
| - **Global prerender env override**: Added `REACT_ON_RAILS_PRERENDER_OVERRIDE=true|false` to force prerender behavior | |
| globally (env > component option > initializer default), useful for CI/test environments without an SSR server. | |
| [PR 2816](https://github.com/shakacode/react_on_rails/pull/2816) by | |
| [justin808](https://github.com/justin808). | |
| [PR 2816](https://github.com/shakacode/react_on_rails/pull/2816) by | |
| [justin808](https://github.com/justin808). | |
| - **`create-react-on-rails-app --pro` support**: Added explicit `--pro` mode to the CLI, including | |
| `react_on_rails_pro` gem installation and generator wiring for Pro-only setup (without requiring `--rsc`). | |
| [PR 2818](https://github.com/shakacode/react_on_rails/pull/2818) by | |
| [justin808](https://github.com/justin808). |
| def prerender_env_override | ||
| PRERENDER_OVERRIDE_CACHE_MUTEX.synchronize do | ||
| raw_value = ENV.fetch(PRERENDER_OVERRIDE_ENV_KEY, nil) | ||
| cached_override = @prerender_env_override_cache | ||
| return cached_override[:value] if cached_override && cached_override[:raw_value] == raw_value | ||
|
|
||
| parsed_value = parse_prerender_env_override(raw_value) | ||
| @prerender_env_override_cache = { raw_value: raw_value, value: parsed_value } | ||
| parsed_value | ||
| end | ||
| end |
There was a problem hiding this comment.
The mutex is acquired on every prerender call (once per rendered component per request), because ENV.fetch is called inside synchronize to compare with the cached raw value. In production, env vars never change after boot, so this adds lock contention for no real benefit.
Consider caching once and never re-reading:
def prerender_env_override
return @prerender_env_override_cache[:value] if @prerender_env_override_cache
PRERENDER_OVERRIDE_CACHE_MUTEX.synchronize do
# Double-checked locking: another thread may have populated the cache while we waited
return @prerender_env_override_cache[:value] if @prerender_env_override_cache
raw_value = ENV.fetch(PRERENDER_OVERRIDE_ENV_KEY, nil)
@prerender_env_override_cache = { value: parse_prerender_env_override(raw_value) }
@prerender_env_override_cache[:value]
end
endThis pays the mutex cost exactly once and is zero-overhead for all subsequent requests. reset_prerender_env_override_cache! still works for test isolation.
| it "falls back to configured behavior for invalid env values" do | ||
| ENV["REACT_ON_RAILS_PRERENDER_OVERRIDE"] = "definitely-not-boolean" | ||
| ReactOnRails.configuration.prerender = true | ||
|
|
||
| expect(Rails.logger).to receive(:warn) | ||
| .with(/Ignoring REACT_ON_RAILS_PRERENDER_OVERRIDE/) | ||
| .once | ||
|
|
||
| attrs = the_attrs | ||
| opts = described_class.new(**attrs) | ||
|
|
||
| expect(opts.prerender).to be true | ||
| expect(opts.prerender).to be true | ||
| end |
There was a problem hiding this comment.
The .once expectation passes here, but for the wrong reason: the second opts.prerender call hits the class-level cache and never re-enters parse_prerender_env_override at all — it's not that Rails.logger is deduplicated, it's that the warning path is simply never reached again.
The actual intent is presumably to verify that a second independent RenderOptions instance doesn't re-warn. Consider a test that better captures that:
opts1 = described_class.new(**attrs)
opts2 = described_class.new(**attrs)
expect(opts1.prerender).to be true
expect(opts2.prerender).to be true # cache hit; no second warningwith expect(Rails.logger).to receive(:warn).once wrapping both calls.
| end | ||
| end | ||
|
|
||
| def reset_prerender_env_override_cache! |
There was a problem hiding this comment.
reset_prerender_env_override_cache! is part of the public API only to support test isolation. Exposing it publicly leaks an implementation detail and risks accidental calls in production code.
Consider marking it as available only in test environments (e.g., in a spec/support helper that reopens the class) or document it explicitly as a test-only method, similar to Rails' own Rails.cache.clear conventions.
Review: Add REACT_ON_RAILS_PRERENDER_OVERRIDE global prerender overrideOverall the feature is well-scoped and the implementation is correct. Four issues worth addressing before merge: Bug — CHANGELOG entry is malformed (must fix) The new entry was inserted between PR 2818's Performance — mutex acquired on every
Test — "warn once" assertion tests cache behavior, not the stated intent The Minor — This method is public only to enable test isolation. Worth at minimum documenting it as test-only, or moving it into a spec support helper that reopens the class, to avoid accidental use in application code. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: daa8421281
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| env_override = prerender_env_override | ||
| return env_override unless env_override.nil? |
There was a problem hiding this comment.
Exempt forced-hash SSR path from global false override
Returning the env override before option/config lookup makes REACT_ON_RAILS_PRERENDER_OVERRIDE=false disable SSR even when a caller explicitly requires it, and react_component_hash is one such caller (Helper#react_component_hash still sets options[:prerender] = true and expects an Object result). In that configuration, server_rendered_react_component returns empty-string HTML instead of a Hash and react_component_hash raises at runtime, so apps using that helper can fail as soon as the global override is set. Fresh evidence in this commit is the new early return in RenderOptions#prerender, which now takes precedence over the helper’s explicit prerender: true contract.
Useful? React with 👍 / 👎.


Summary
REACT_ON_RAILS_PRERENDER_OVERRIDEsupport inRenderOptions#prerenderCloses #2763
Testing
bundle exec rspec react_on_rails/spec/react_on_rails/react_component/render_options_spec.rbbundle exec rubocop react_on_rails/lib/react_on_rails/react_component/render_options.rb react_on_rails/spec/react_on_rails/react_component/render_options_spec.rbNote
Medium Risk
Introduces a new environment-variable-driven override that can force SSR on/off across all
react_componentrenders, which could change rendering behavior in any environment where the variable is set. Default behavior is unchanged when the env var is absent, and invalid values fall back with a logged warning.Overview
Adds a global prerender override via
REACT_ON_RAILS_PRERENDER_OVERRIDE=true|false, making prerender precedence env > per-component option > initializer default.Implements a thread-safe cached parser for the env value (including normalization and one-time warnings on invalid values), expands
RenderOptionsspecs to cover override/precedence behavior, and documents the new override in the view helper docs andCHANGELOG.Written by Cursor Bugbot for commit daa8421. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
New Features
Documentation
Tests
Chores