Skip to content

Add REACT_ON_RAILS_PRERENDER_OVERRIDE global prerender override#2816

Merged
justin808 merged 6 commits intomainfrom
jg-codex/2763-prerender-override
Mar 24, 2026
Merged

Add REACT_ON_RAILS_PRERENDER_OVERRIDE global prerender override#2816
justin808 merged 6 commits intomainfrom
jg-codex/2763-prerender-override

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Mar 24, 2026

Summary

  • add REACT_ON_RAILS_PRERENDER_OVERRIDE support in RenderOptions#prerender
  • make env override precedence explicit: env > component option > config default
  • add spec coverage for true/false overrides and invalid-value fallback
  • document the env override in view helper docs

Closes #2763

Testing

  • bundle exec rspec react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb
  • bundle 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.rb

Note

Medium Risk
Introduces a new environment-variable-driven override that can force SSR on/off across all react_component renders, 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 RenderOptions specs to cover override/precedence behavior, and documents the new override in the view helper docs and CHANGELOG.

Written by Cursor Bugbot for commit daa8421. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Added a global prerender override via REACT_ON_RAILS_PRERENDER_OVERRIDE with precedence: environment variable > component option > initializer default.
  • Documentation

    • Updated API docs to describe the new prerender override, expected values, normalization, and evaluation order.
  • Tests

    • Added tests for env-var override precedence, normalization, invalid-value warning + fallback, and cache/reset behavior.
  • Chores

    • Added an unreleased changelog entry documenting the change.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 24, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Added a REACT_ON_RAILS_PRERENDER_OVERRIDE environment variable to globally override prerender behavior; RenderOptions#prerender now checks this env var (normalized "true"/"false") before per-call prerender: and initializer default. Docs and tests updated to document and validate the new precedence.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md, docs/oss/api-reference/view-helpers-api.md
Documented the new REACT_ON_RAILS_PRERENDER_OVERRIDE env var, accepted values (true/false), and precedence: env > component prerender: option > initializer default.
Implementation
react_on_rails/lib/react_on_rails/react_component/render_options.rb
Added PRERENDER_OVERRIDE_ENV_KEY, PRERENDER_OVERRIDE_VALUES, a mutex-protected parse/cache for the env value, prerender_env_override helpers, reset_prerender_env_override_cache!, and updated #prerender to prefer a valid env override while warning on invalid values.
Tests
react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb
Added helper to clear/restore the env and cache, new tests for env-driven prerender behavior ("true", "false", whitespace/case normalization, invalid values emitting one warning), and wrapped :prerender shared assertions to run with the env override cleared.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble on code beneath the moon,
A tiny var makes prerender tune.
True hops forward, False stays home,
Tests now run without an SSR comb.
Docs and specs—my carrot's in bloom.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: introducing a global prerender override via the REACT_ON_RAILS_PRERENDER_OVERRIDE environment variable.
Linked Issues check ✅ Passed The PR fully implements Option B from issue #2763: adds REACT_ON_RAILS_PRERENDER_OVERRIDE env var with correct precedence (ENV > component option > config default), parses values safely, caches results, handles invalid values with warnings, includes comprehensive tests, and updates documentation.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the prerender override feature: environment variable parsing in RenderOptions, documentation updates, CHANGELOG entry, and focused test coverage with helper utilities for proper test isolation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jg-codex/2763-prerender-override

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread react_on_rails/lib/react_on_rails/react_component/render_options.rb
Comment thread react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 24, 2026

Review

The feature is well-scoped and the intent is clear. Three issues worth addressing before merge:

Must-fix

Warning floods logs on every render (see inline comment on render_options.rb)
prerender_env_override is called on every #prerender invocation, so an invalid env var value will emit a Rails.logger.warn on every component render on every request. In a busy app that is high-traffic this means thousands of warnings per minute. The env var is a boot-time global, so it should be parsed and cached once at class-load time rather than re-evaluated per render.

Should-fix

around hook scope too broad (see inline comment on the spec)
The env cleanup hook is defined at the top level of the describe block, wrapping every test in the file. It should be scoped to just the #prerender env override block.

Missing CHANGELOG entry
Per project guidelines, user-visible features need an entry under #### Added in CHANGELOG.md. This adds a new global override mechanism that operators need to know about.

Nit

The invalid-value test sets up the expect(Rails.logger).to receive(:warn) expectation after constructing opts but before calling opts.prerender. It works because the method is lazy, but adding .once and moving the expectation to the top of the test would make the contract explicit and more resilient to refactoring. See inline comment.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread react_on_rails/lib/react_on_rails/react_component/render_options.rb Outdated
Comment thread react_on_rails/lib/react_on_rails/react_component/render_options.rb
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 24, 2026

Greptile Summary

This PR introduces a REACT_ON_RAILS_PRERENDER_OVERRIDE environment variable that can force server-side rendering on or off globally, overriding both per-component prerender: options and the initializer default. The implementation is clean and fits naturally into the existing RenderOptions precedence chain (env → component option → config default).

Key changes:

  • render_options.rb: Adds prerender_env_override private method that reads and normalises the env var, logs a warning for invalid values, and returns nil to fall through to existing logic.
  • render_options_spec.rb: Adds an around hook for safe env isolation and three new examples covering the happy paths and invalid-value fallback.
  • view-helpers-api.md: Documents the new env var and its precedence.

Minor concerns:

  • prerender_env_override is called on every render. If the env var is set to an invalid value, Rails.logger.warn fires on every rendered component, which could flood production logs. Caching the parsed result at the class level would eliminate both the per-request overhead and the warning noise.
  • In the invalid-value spec, the expect(Rails.logger).to receive(:warn) message expectation is set up after the object is instantiated but before #prerender is called — technically correct, but moving it before described_class.new would follow conventional RSpec ordering and make the test more robust against future refactors.

Confidence Score: 5/5

  • This PR is safe to merge — the feature is well-scoped, correctly implemented, and fully tested.
  • The core logic is simple and correct: env var lookup → normalise → return boolean or nil → fall through to existing precedence chain. The around hook correctly isolates tests. The two P2 suggestions (potential warn-flood on invalid values, and spec ordering convention) are non-blocking quality improvements that don't affect production correctness in normal use (invalid env var value is an operator error, not a code bug).
  • No files require special attention.

Important Files Changed

Filename Overview
react_on_rails/lib/react_on_rails/react_component/render_options.rb Adds PRERENDER_OVERRIDE_ENV_KEY/PRERENDER_OVERRIDE_VALUES constants and a prerender_env_override private method; updates #prerender to check env override first. Logic is correct; minor concern around per-render ENV lookup and warn-flooding on invalid values.
react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb Adds around block for safe env var isolation and three new examples covering true/false overrides and invalid-value fallback. Test ordering for the warn expectation is technically correct but could be clearer.
docs/oss/api-reference/view-helpers-api.md Documentation correctly describes the new env var and its precedence over component option and config default. No issues.

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
Loading

Reviews (1): Last reviewed commit: "Document prerender override in changelog" | Re-trigger Greptile

Comment thread react_on_rails/lib/react_on_rails/react_component/render_options.rb
Comment thread react_on_rails/spec/react_on_rails/react_component/render_options_spec.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 24, 2026

Review Summary

The 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)

prerender_env_override is invoked on every call to #prerender. If a page renders 20 components and REACT_ON_RAILS_PRERENDER_OVERRIDE is set to a typo'd value (e.g. 1 or yes), you'll get 20 identical warnings per request — potentially burying the real misconfiguration signal in production logs. See inline comment for a suggested fix using instance_variable_defined? (needed because false is a valid cached value, making simple ||= memoization incorrect).

Minor notes

  • Test: Adding .once to the receive(:warn) expectation in the invalid-value test would make it self-documenting and catch regressions if the warning ever becomes de-duplicated upstream.
  • Test isolation comment: The around block is necessary to protect the existing configurable_options loop tests for :prerender, but this dependency isn't obvious — a brief comment would help future maintainers understand why the hook is at the top-level describe scope.

Looks good

  • Precedence logic is correct (env > component option > config default).
  • raw_value.strip.downcase handles whitespace and casing nicely.
  • The around block correctly restores CI env state.
  • CHANGELOG and docs are updated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2171ac2 and f3c9601.

📒 Files selected for processing (4)
  • analysis/open-issue-deep-dive/README.md
  • analysis/open-issue-deep-dive/wave-1-active.md
  • analysis/open-issue-deep-dive/wave-2-backlog.md
  • analysis/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

Comment thread analysis/open-issue-deep-dive/wave-2-backlog.md
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 24, 2026

Code Review

The implementation is clean and the precedence logic is correct. A few issues worth addressing.

Significant: Warning fires on every component render

prerender_env_override is called on every invocation of #prerender (once per component per request). When REACT_ON_RAILS_PRERENDER_OVERRIDE is set to an invalid value, Rails.logger.warn fires for every component rendered on every request — very noisy in production.

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, ReactOnRails.configuration.prerender = true is set but never restored, risking test pollution for later examples that rely on the default config value.

Minor: Missing explicit "env var absent" test in the new describe block

The new describe "#prerender env override" group does not include a case verifying that when the env var is absent the normal configuration chain applies. It is implicitly covered by existing tests but an explicit case would make the group self-documenting.

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.

Comment thread react_on_rails/lib/react_on_rails/react_component/render_options.rb
@justin808 justin808 force-pushed the jg-codex/2763-prerender-override branch from f3c9601 to d220132 Compare March 24, 2026 00:24
Comment thread react_on_rails/lib/react_on_rails/react_component/render_options.rb Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 24, 2026

Review Summary

The 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

  • The around block correctly handles test isolation — good practice.
  • The configurable_options shared test for :prerender still passes correctly because the around block clears the env var before each example.
  • ENV.fetch(key, nil) vs ENV[key] is a minor style point; both work fine.

Overall a useful escape hatch for CI/test environments. The log flooding issue is the main concern.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

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.

Create PR

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

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 24, 2026

PR Review: Add REACT_ON_RAILS_PRERENDER_OVERRIDE global prerender override

The 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)

RenderOptions.prerender_env_override reads and writes the class-level instance variable @prerender_env_override_cache without any synchronization. In a multi-threaded server (Puma, Falcon) two threads can interleave between the read and the write. The worst case is double-parsing (idempotent), but the class-level mutable state is an unnecessary risk. ENV does not change between requests in production, so the cache buys very little. Consider either removing the cache entirely (ENV reads + one hash lookup are negligible) or protecting writes with a Mutex.

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: .once expectation couples test to caching implementation (Medium)

The .once assertion on the Rails.logger.warn expectation passes only because the class-level cache suppresses the second parse call. If caching is ever removed or refactored, this test fails with a misleading error. The test should assert observable behavior (value falls back, warning is emitted) without pinning how many times the warning fires internally. Use .at_least(:once) or avoid calling prerender twice in the same example.

with_prerender_env_override_cleared does not reset the class cache (Low)

The helper restores the ENV variable correctly but leaves @prerender_env_override_cache populated. Tests pass today because the cache comparison re-detects any ENV change. This is a subtle dependency: a future caching refactor could silently break test isolation. An explicit cache reset in the ensure block would make the isolation explicit.

build_render_options_with_clean_prerender_env helper complexity (Low)

This helper branches on the option name to decide whether to clear the env. A single top-level around block that unconditionally clears the env and the class cache for the entire spec would achieve the same isolation without the per-call conditional.


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.

Comment thread react_on_rails/lib/react_on_rails/react_component/render_options.rb Outdated
@justin808
Copy link
Copy Markdown
Member Author

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)
27 examples, 0 failures

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

Comment on lines +43 to +46
Rails.logger.warn(
"[REACT ON RAILS] Ignoring #{PRERENDER_OVERRIDE_ENV_KEY}=#{raw_value.inspect}. " \
"Expected 'true' or 'false'."
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +162 to +168
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!
  # ...
end

This pattern should be applied consistently across all tests in this describe block that set the env var directly.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 24, 2026

Review Summary

The feature itself is well-conceived — a global REACT_ON_RAILS_PRERENDER_OVERRIDE env var with clear precedence (env > component option > config default) is exactly the right UX for CI/test environments. The test coverage is thorough and the docs update is clear.

However, there are a few implementation concerns worth addressing before merging:

Critical: Mutex on the render hot path

PRERENDER_OVERRIDE_CACHE_MUTEX.synchronize is called on every component render. In high-concurrency Puma/multi-threaded environments, a single global Mutex on the render path is a contention bottleneck. See the inline comment on prerender_env_override — the cache re-reads ENV.fetch on every call anyway, so the mutex adds overhead without preventing ENV reads. The simplest fix is to either:

  • Cache once at startup (ENV is static after boot)
  • Or drop the cache/mutex entirely and just parse on each call (it's a hash lookup, effectively free)

Medium: Logger called inside mutex

Rails.logger.warn is invoked while holding PRERENDER_OVERRIDE_CACHE_MUTEX. Logger subscribers can involve I/O or their own locks, which risks deadlock. See the inline comment for a suggested refactor to emit the warning outside the critical section.

Minor: reset_prerender_env_override_cache! is public API

This method exists only to support test isolation. Leaving it as a public class method means application code could accidentally call it in production, corrupting the cache mid-flight in a multi-threaded app. Consider guarding it with a Rails.env.test? check or noting it clearly as test-only.

Minor: Inconsistent cache-reset discipline in specs

Several tests set ENV[...] directly without calling reset_prerender_env_override_cache! afterward. They pass today because the around block clears the cache first, but the pattern is fragile. See the inline comment on the spec.


Overall: Good feature, clean implementation approach — just wants a simpler/safer caching strategy before landing.

@justin808 justin808 force-pushed the jg-codex/2763-prerender-override branch from e8b1675 to daa8421 Compare March 24, 2026 00:54
@justin808 justin808 merged commit c8267da into main Mar 24, 2026
12 checks passed
@justin808 justin808 deleted the jg-codex/2763-prerender-override branch March 24, 2026 00:54
Comment thread CHANGELOG.md
Comment on lines +32 to 35
- **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).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
- **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).

Comment on lines +17 to +27
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
end

This pays the mutex cost exactly once and is zero-overhead for all subsequent requests. reset_prerender_env_override_cache! still works for test isolation.

Comment on lines +195 to +208
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 warning

with expect(Rails.logger).to receive(:warn).once wrapping both calls.

end
end

def reset_prerender_env_override_cache!
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 24, 2026

Review: Add REACT_ON_RAILS_PRERENDER_OVERRIDE global prerender override

Overall 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 by keyword and its author attribution, so PR 2818 loses its author link and it visually attaches only to PR 2816. See inline comment on CHANGELOG.md.

Performance — mutex acquired on every prerender call

ENV.fetch is called inside PRERENDER_OVERRIDE_CACHE_MUTEX.synchronize on every invocation, adding lock contention per rendered component. Since env vars don't change at runtime in production, a simple double-checked locking pattern (read ENV once, cache forever, never re-enter the mutex) would be zero-overhead after the first call. See inline comment on render_options.rb.

Test — "warn once" assertion tests cache behavior, not the stated intent

The .once expectation on the invalid-value test passes because the second opts.prerender call hits the cache rather than because the logger was deduplicated. The test should use two separate RenderOptions instances to actually demonstrate that the class-level cache prevents repeated warnings across distinct objects. See inline comment on the spec.

Minor — reset_prerender_env_override_cache! leaks an implementation detail

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.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +111 to +112
env_override = prerender_env_override
return env_override unless env_override.nil?
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add REACT_ON_RAILS_PRERENDER_OVERRIDE env var to globally disable prerendering

1 participant