Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b06ba43
feat: add rolling_deploy_adapter for seeding previous bundle hashes
justin808 Apr 18, 2026
d37be09
fix: address PR C review feedback for rolling_deploy_adapter
justin808 Apr 18, 2026
1da7a1d
fix: address remaining PR C review feedback (security, robustness, docs)
justin808 Apr 18, 2026
9a8f5fd
Avoid hangs and path escape in rolling deploy seeding
justin808 Apr 18, 2026
2e8dda1
fix: harden rolling deploy adapter follow-ups
justin808 Apr 19, 2026
edcd85c
fix: address rolling-deploy adapter follow-up review feedback
justin808 Apr 19, 2026
aa76baa
Harden rolling deploy follow-up fixes
justin808 Apr 23, 2026
cbb562e
Clarify rolling deploy shared assets
justin808 Apr 23, 2026
f4b0450
Harden rolling deploy review follow-ups
justin808 Apr 23, 2026
e58a589
Harden rolling deploy cache staging cleanup
justin808 Apr 23, 2026
a45f6e9
Tighten rolling deploy upload signature validation
justin808 Apr 23, 2026
c0348bd
Reject extra required rolling deploy upload keywords
justin808 Apr 23, 2026
1efc03c
Tighten rolling deploy staging review fixes
justin808 Apr 23, 2026
b668176
Harden rolling deploy asset staging
justin808 Apr 23, 2026
b792b82
Harden rolling deploy adapter edge cases
justin808 Apr 23, 2026
c786471
Harden rolling deploy adapter validation
justin808 Apr 23, 2026
0b857f9
Polish rolling deploy review follow-ups
justin808 Apr 29, 2026
1648379
Polish rolling deploy review follow-ups (round 2)
justin808 Apr 29, 2026
6f0fb46
Reject *args + explicit keywords in rolling_deploy upload signature
justin808 Apr 30, 2026
70db200
Polish rolling deploy review follow-ups (round 3)
justin808 Apr 30, 2026
de926fa
Exclude two more flaky GitHub URLs from markdown link check
justin808 Apr 30, 2026
5ab92dd
Harden rolling deploy staging diagnostics
justin808 Apr 30, 2026
73f8645
Polish rolling deploy review follow-ups (round 4)
justin808 Apr 30, 2026
644ef86
Polish rolling deploy review follow-ups (round 5)
justin808 May 3, 2026
9b40357
Polish rolling deploy review follow-ups (round 6)
justin808 May 5, 2026
c7d14cb
Stabilize rolling deploy cache staging
justin808 May 8, 2026
9180032
Pass RSC manifests to required asset lookup
justin808 May 8, 2026
541816a
Fix rolling deploy review cleanup issues
justin808 May 9, 2026
b5345ef
Document rolling deploy adapter timeout budgets
justin808 May 9, 2026
69a8686
fix(pro): address renderer cache review follow-ups
justin808 May 9, 2026
281a39b
fix: include renderer cache path in Dockerfile hint
justin808 May 9, 2026
0f7bbd5
fix: address renderer cache doctor follow-ups
justin808 May 9, 2026
90c0d82
fix: address final renderer cache review notes
justin808 May 9, 2026
abbca67
fix: close renderer cache review follow-ups
justin808 May 9, 2026
7e6a596
Address rolling deploy review feedback (round 7)
justin808 May 10, 2026
3cd40c2
fix: validate selected JavaScript package manager
justin808 May 7, 2026
5f13cf0
Remove unreachable duplicate File.exist? guard in restore_previous_bu…
justin808 May 10, 2026
8606c03
fix: address renderer cache review round 7
justin808 May 10, 2026
16f75c6
feat(pro): make assets:precompile pre-seed mode configurable via ENV
justin808 May 10, 2026
0429832
fix: address rolling-deploy review feedback (round 9)
justin808 May 11, 2026
acec409
Warn when rolling deploy bundles are missing
justin808 May 12, 2026
599ec84
fix: address rolling-deploy review feedback
justin808 May 12, 2026
0dd52cb
Harden rolling deploy review fixes
justin808 May 12, 2026
ba13457
fix(pro): repair rolling-deploy review regressions for required_rsc_a…
justin808 May 21, 2026
ca729db
fix: address rolling-deploy review feedback (round 10)
justin808 May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .lychee.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ exclude = [
'^https://github\.com/shakacode/react_on_rails/issues/2766$', # Intermittent 502 from GitHub issue pages in CI
'^https://github\.com/rack/rack/blob/main/SPEC\.rdoc$', # Intermittent 502 from GitHub blob pages in CI
'^https://github\.com/shakacode/shakapacker/blob/cdf32835d3e0949952b8b4b53063807f714f9b24/package/environments/base\.js(#.*)?$', # Intermittent 502 from GitHub blob view in CI
'^https://github\.com/K4sku$', # Intermittent 502 from GitHub user page in CI
'^https://github\.com/npm/cli/issues/6253$', # Intermittent 502 from GitHub issue pages in CI
'^https://github\.com/search\?q=gem\+react_on_rails&ref=advsearch&type=repositories&utf8=%E2%9C%93$', # Intermittent 502 from GitHub search in CI

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@ After a release, run `/update-changelog` in Claude Code to analyze commits, writ
- **[Pro]** **Pre-seed renderer cache for Docker builds**: New `react_on_rails_pro:pre_seed_renderer_cache` rake task copies compiled server bundles into the Node Renderer's bundle-hash cache directory structure during Docker image builds, eliminating the 410→retry cold-start latency (200ms–1s+) on the first SSR request after deployment. Supports `RENDERER_SERVER_BUNDLE_CACHE_PATH`, RSC bundles, and rolling-deploy guidance centered on current and previous bundle hashes. The legacy `pre_stage_bundle_for_node_renderer` task now stages the same cache layout via symlinks for same-filesystem workflows. **Note:** `RENDERER_BUNDLE_PATH` is now deprecated in favor of `RENDERER_SERVER_BUNDLE_CACHE_PATH` across both tasks. Existing users with `RENDERER_BUNDLE_PATH` set will see a deprecation warning on stderr. [PR 3124](https://github.com/shakacode/react_on_rails/pull/3124) by [justin808](https://github.com/justin808).
- **[Pro]** **Async props with incremental React Server Component rendering**: Added the `stream_react_component_with_async_props` and `rsc_payload_react_component_with_async_props` view helpers, which accept a block to declare props that are fetched concurrently and streamed to the rendering component as each value becomes available. The React component renders its shell immediately (with `<Suspense>` fallbacks) and progressively re-renders as async prop promises resolve, dramatically improving Time to First Byte for pages with slow data fetches. Components access async props through the `getReactOnRailsAsyncProp` function injected into props (typed via the new `WithAsyncProps` TypeScript helper). Requires `config.enable_rsc_support = true`. This is an additive feature — existing `stream_react_component` and `rsc_payload_react_component` calls are unaffected. [PR 2903](https://github.com/shakacode/react_on_rails/pull/2903) by [AbanoubGhadban](https://github.com/AbanoubGhadban).

- **[Pro]** **Rolling-deploy adapter protocol**: New `config.rolling_deploy_adapter` pluggable module (protocol: `previous_bundle_hashes`, `fetch`, `upload`) that seeds previously-deployed bundle hashes into the Node Renderer cache, preventing 410→retry for draining-version requests during rolling deploys. `assets:precompile` auto-calls `upload` in production-like environments so the next deploy can fetch the just-built bundle. `PREVIOUS_BUNDLE_HASHES` env var overrides discovery for CI. `react_on_rails:doctor` probes the adapter and reports protocol conformance, discovery latency, and resolved cache dir. Each seeded hash carries its own `loadable-stats.json` / RSC manifests so client-side hydration stays consistent with the deployed asset pipeline for that hash. See `docs/pro/rolling-deploy-adapters.md` for the full protocol spec and reference implementations (S3, Control Plane, Filesystem). [PR 3173](https://github.com/shakacode/react_on_rails/pull/3173) by [justin808](https://github.com/justin808).

#### Changed

- **[Pro]** **`PreSeedRendererCache` and `PrepareNodeRenderBundles` now auto-stage `loadable-stats.json`**: `ReactOnRailsPro::RendererCacheHelpers.collect_assets` now appends `loadable-stats.json` whenever the file exists on disk, so every caller (rolling-deploy seeding, `pre_seed_renderer_cache`, `pre_stage_bundle_for_node_renderer`) stages it automatically. **Action required for upgraders:** if your `assets_to_copy` config explicitly listed `loadable-stats.json`, remove that entry — otherwise you'll see a "Duplicate asset basenames in assets_to_copy" warning on every stage. The duplicate is harmless (`stage_assets` keeps the last entry per basename), but the warning is noise. [PR 3173](https://github.com/shakacode/react_on_rails/pull/3173) by [justin808](https://github.com/justin808).
- **[Pro]** **Unified renderer cache staging**: `ReactOnRailsPro::PreSeedRendererCache.call(mode: :copy | :symlink)` is now the single entry point for staging the Node Renderer cache. Both modes produce the same `<cache>/<bundleHash>/<bundleHash>.js` layout. The `react_on_rails_pro:pre_seed_renderer_cache` rake task accepts `MODE=copy` (default; Docker/image builds) or `MODE=symlink` (same-filesystem). The auto-invocation at the end of `assets:precompile` defaults to `:symlink` (preserving prior behavior) and now honors `ASSETS_PRECOMPILE_RENDERER_CACHE_MODE=copy|symlink` so Docker builds that run `rake assets:precompile` as the final asset step can opt into copy mode without invoking the rake task separately. `MODE=copy` raises a clear error when neither `RENDERER_SERVER_BUNDLE_CACHE_PATH` nor `RENDERER_BUNDLE_PATH` is set in non-dev/test environments, because the Node renderer's default lookup can differ from the Ruby side and would silently drop pre-seeded bundles in the wrong directory. The legacy `react_on_rails_pro:pre_stage_bundle_for_node_renderer` task and `ReactOnRailsPro::PrepareNodeRenderBundles` class remain as deprecated shims that emit a once-per-process warning and delegate to `mode: :symlink`. `react_on_rails:doctor` flags deploy scripts that still reference the deprecated task. **Heads-up for custom scripts:** the previous flat layout wrote `$RENDERER_BUNDLE_PATH/<renderer_bundle_file_name>`; any external scripts (health checks, renderer launchers) that read that path directly must now read `$RENDERER_SERVER_BUNDLE_CACHE_PATH/<bundleHash>/<bundleHash>.js` instead. [PR 3124](https://github.com/shakacode/react_on_rails/pull/3124) by [justin808](https://github.com/justin808).
- **[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).
- **[Pro] Documentation standardized on `REACT_RENDERER_URL` env var name**: The configuration example in `docs/oss/configuration/configuration-pro.md` now shows `ENV["REACT_RENDERER_URL"]` instead of the older `ENV["RENDERER_URL"]`, aligning with the rest of the docs and the generator template. Existing apps that read `ENV["RENDERER_URL"]` in their initializer continue to work — the Pro `renderer_url` config is whichever env var the user reads in their initializer; no gem code reads either name directly. Rename the env var in your infrastructure configs (and update the initializer to match) if you want to align with the new convention. `bin/dev` now also warns when `RENDERER_URL` is set without `REACT_RENDERER_URL` so the rename doesn't silently fall back to the default renderer URL. [PR 3142](https://github.com/shakacode/react_on_rails/pull/3142) by [justin808](https://github.com/justin808).
Expand Down
23 changes: 16 additions & 7 deletions docs/pro/node-renderer.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,19 +171,28 @@ RUN bundle exec rake react_on_rails_pro:pre_seed_renderer_cache

### Rolling deploys: seed current and previous bundle hashes

During a rolling deploy, new renderer instances can receive requests for both the current deployed bundle hash and the previous hash while old Rails instances drain. Treat this as a two-hash cache-seeding problem, not a single-file problem.
During a rolling deploy, new renderer instances can receive requests for both the current deployed bundle hash and the previous hash while old Rails instances drain. Treat this as a two-hash cache-seeding problem, not a single-file problem — and each seeded hash must carry its own companion `loadable-stats.json` / RSC manifests built in lockstep with that bundle.

At startup, aim to have the cache contain:
`pre_seed_renderer_cache` handles the current bundle. For previous hashes, configure a **`rolling_deploy_adapter`** that:

- the current server bundle hash
- the previous server bundle hash
- the current and previous RSC bundle hashes as well, if RSC support is enabled
- any required copied assets and RSC manifests in each seeded hash directory
- Publishes each successful deploy's bundle + companion assets to an artifact store (S3, Control Plane image registry, etc.) via its `upload` method — called automatically after `assets:precompile` in production-like environments.
- Advertises recent deploys' bundle hashes via `previous_bundle_hashes`.
- Retrieves the bundle + assets for a given historical hash via `fetch`.

`pre_seed_renderer_cache` seeds the current locally built bundle outputs. For the previous deployed hash, the most practical approach is to publish bundle artifacts keyed by hash after each successful deploy, then fetch the previous hash artifact during the next build and place it into the same `<cache>/<bundleHash>/...` layout before boot.
```ruby
# config/initializers/react_on_rails_pro.rb
ReactOnRailsPro.configure do |config|
config.rolling_deploy_adapter = MyApp::S3RollingDeployAdapter
end
```

During the next build, `pre_seed_renderer_cache` calls `previous_bundle_hashes`, deduplicates against the current hash, then fetches and stages each into `<cache>/<bundleHash>/...` — preventing 410→retry for draining-version requests.

See [Rolling-Deploy Adapters](./rolling-deploy-adapters.md) for the full protocol spec, reference implementations (S3, Control Plane, Filesystem), and a discussion of the loadable-stats wrinkle.

## Further Reading

- [Rolling-Deploy Adapters](./rolling-deploy-adapters.md) — Protocol spec and reference implementations for `rolling_deploy_adapter`
- [Node Renderer basics](../oss/building-features/node-renderer/basics.md) — Architecture and core concepts
- [JavaScript configuration](../oss/building-features/node-renderer/js-configuration.md) — Node-side config options
- [Error reporting and tracing](../oss/building-features/node-renderer/error-reporting-and-tracing.md) — Monitoring in production
Expand Down
Loading
Loading