Fixed
-
[Pro] Expected Node Renderer cold starts no longer emit OpenTelemetry error spans: The
ror.bundle.build_execution_contextcache-first probe previously ended with status ERROR when a worker had not
compiled a bundle's VM context yet, even though the normal cache-miss path then rendered successfully. The probe
now treats that expected miss as normal control flow while preserving error spans for genuine failures.
#4908 by
sashakhar1. -
[Pro] Hydrated Redux stores no longer leak across Turbo/Turbolinks navigations: The hydrated-store
registry is now cleared on client-side page unload (soft navigation), alongside the existing component
teardown. Previously a leftover entry from the previous page madegetOrWaitForStoreresolve immediately
with the previous page's store, silently defeating thestore_dependencieshydration gate — most visibly
with deferred stores (redux_store(..., defer: true)), where a mid-page component could render the
previous page's Redux state instead of waiting for its own page's hydration data. Registered store
generators are unaffected and persist across navigations. Note for apps that relied on a store surviving a
soft navigation without re-rendering its hydration data: each page using a store must now render its own
redux_storecall (the documented model). Fixes
#4861.
#4871 by
AbanoubGhadban. -
On-demand renders initialize only the island's declared store dependencies:
reactOnRailsComponentLoadedwalked every store element on the page even when asked to render a
single component, so an unrelated store element whose generator was not registered (e.g. its
bundle had not loaded yet) aborted the requested render, and unrelated new stores were hydrated
as a side effect. It now reads thedata-store-dependenciesattribute thereact_component
helper already emits (defaulted to the stores registered in the request, or set explicitly with
thestore_dependenciesoption) and initializes exactly those stores, matching the Pro
renderer's dependency gating; a declared dependency that cannot initialize is logged without
aborting the render. Markup without the attribute keeps the previous initialize-every-store
behavior, andreactOnRailsPageLoaded/full page loads are unchanged. Completes
#4862.
#4870 by
AbanoubGhadban. -
[Pro] Rails requests to the Node Renderer once again continue OpenTelemetry traces: The async-http transport
now creates a CLIENT span and injects W3C trace context for regular and streaming renders, incremental async-props
renders, raw-render requests, and asset uploads when the Rails application has configured the OpenTelemetry SDK.
OpenTelemetry remains optional, and spans record only the HTTP method, normalized request path, response status, and
request/response byte sizes. Fixes
#4866.
#4869 by
sashakhar1. -
[Pro] Node Renderer transport follow-ups now expose protocol errors and accurate Fastify modes:
Rails retries continue for network disconnects and peer-reset HTTP/2 streams, while HTTP parser and framing errors
surface directly. PublicconfigureFastifycallbacks andfastifyServerOptionsnow reflect both HTTP/1.1 and
HTTP/2 runtime modes.
#4893 by
sashakhar1. -
[Pro] Bounded Node Renderer VM retention now avoids old/new RSC rebuild thrash during rolling deploys:
The default per-worker VM hard cap now retains four contexts, enough for the server and RSC bundles from one
draining and one current revision. Successful bundle sets remain reusable through a configurable, timer-driven
drain window, while inactive contexts, generation metadata, and pressure logs stay bounded. Pre-seeding now emits
an immutable revision-scoped current-generation declaration; each configured renderer worker validates and compiles
that complete server/RSC set before listening, pins it across old-only traffic gaps, and reports ready only after the
compile barrier. Symlink-mode cache paths reuse the validated immutable snapshot VM identity without rebuilding on
the first request. Doctor now requires valid renderer JavaScript before launcher-derived capacity can prove a warm
pass, and reports observed/unverified declaration evidence rather than claiming success from invalid syntax,
loopback, or Rails-process configuration. Upgrade memory impact: the
defaultmaxVMPoolSizedoubles from 2 to 4 per worker, and total VM retention scales with renderer workers and
replicas, so operators should re-check deployment memory requests and limits. InvalidMAX_VM_POOL_SIZEvalues
that previously fell back to the default now fail fast during renderer startup. Fixes
#4810.
#4811 by
justin808. -
Routine startup diagnostics no longer appear in default
INFOlogs: Successful package validation, valid
Pro license checks, non-production missing-license notices, and Node renderer connection setup now log atDEBUG
instead ofINFO. Package validation still runs, while expired or invalid configured licenses remain visible outside
production, and production license warnings and renderer failures remain actionable. Fixes
#4848.
#4849 by
Justin Gordon. -
Generated server webpack configs no longer include an unused
mergeimport or stale comments:
commonWebpackConfigalready clones the shared client configuration, so the generated server configuration
stays lint-clean without changing its runtime behavior. Fixes
#4791.
#4840 by
ihabadham. -
Release retries now durably reuse maintainer-verified ShakaPerf evidence:
rake releasecan bind an
existing successful run to the canonical release tracker withRELEASE_SHAKAPERF_RUN, then re-fetch and
re-verify the exact SHA or machine-proven runtime-equivalent evidence on later invocations instead of
dispatching duplicate performance runs. Tracker trust is bound to the exact
Release gate: react_on_rails X.Y.Zstable-base title, explicit selectors take precedence over automatic
accepted-RC reuse, and persisted accelerated retries cannot silently consume a selector. Automatic reuse
falls back to normal discovery only for authoritative natural invalidation such as stale or missing evidence,
a failed/cancelled live run, or proven runtime divergence; indeterminate API or Git failures, detector errors,
and record mutations remain blocking. Raw REST-selected runs now preserve their creation timestamp through
strict-final verification. Before either a live release or dry run can reach registry checks, confirmation,
mutation, tagging, or publication, the task also verifies the frozen pnpm install state, the repository-pinned
pnpm version, and lifecycle-enabled builds of all four npm release packages before creating a release checkout,
pulling, authenticating, or reading any remote release state. The successful check is bound to the exact commit;
when a livegit pull --rebaseadvancesHEAD, the task rebuilds and rebinds readiness before resolving the release
version or continuing. npm publication now retries only explicit OTP challenges and context-qualified transient
network/HTTP failures; successful lifecycle/tool banners no longer mask those diagnostics, while authentication,
actual lifecycle failures, registry rejection, incidental numeric diagnostics, and unknown failures stop immediately
with OTP values redacted. Saved ShakaPerf evidence also recognizes GitHub CLI's exact
no valid artifacts found to downloaddiagnostic as authoritative absence while preserving observation failures as
blocking. Stable releases also support
an append-only, tracker-bound schema-v2 observation waiver bound to the exact run attempt and canonical
repository/workflow/event/branch identity. Legacy schema-v1 waiver markers remain readable audit history but cannot
authorize publication. The waiver does not
claim the run succeeded or bypass any other release gate; the tracker, waiver, and exact run are revalidated
before remote tag push and package publication, and a rerun attempt blocks both boundaries. Fixes
#4812.
#4833 by
justin808. -
[Pro] RSC render-error details no longer reach browser-facing payloads in production-like
environments: Fetched RSC payload metadata now uses a fail-closed allowlist that exposes only the
generichasErrorssignal needed by client error boundaries. Inline error-bearing payload chunks now
also suppress console replay inproduction,staging, and unrecognized environments, closing a path
that could repeat the server error message or source-mapped file paths after diagnostic metadata was
redacted. Full diagnostics and console replay remain available indevelopmentandtest, clean
production chunks retain console replay, and server-side reporting still receives the original error
details before the browser-boundary redaction runs. Fixes
#4736,
#4822, and
#4827.
#4821 and
#4856 by
justin808. -
HTTP-served SSR bundle loading now honors the response charset, rejects non-2xx responses,
and no longer leaks URL credentials into error messages:
Whenserver_bundle_js_fileresolves to an HTTP(S) URL,RubyEmbeddedJavaScript.file_url_to_string
no longer assumes theContent-Typeheader always ends in an exact; charset=...form. A response
with no charset, aContent-Typewith no charset parameter, a missingContent-Typeheader, or a
quoted charset value (charset="ISO-8859-1") now transcodes to UTF-8 successfully instead of
raising or silently mislabeling the bytes, falling back to UTF-8 when no usable charset is declared.
A non-2xx response (for example a 404 page or a proxy error) is now rejected with a clear
bundle-load error naming the URL and status, instead of being returned as if it were JavaScript
bundle source. Additionally, aserver_bundle_js_fileURL with embedded HTTP basic-auth
credentials (e.g.https://:password@host:3800/bundle.js) no longer leaks that password into
raised errors or logs on a load failure. Fixes
#4584.
#4817 by
justin808. -
RailsContext now stays current across Turbo and Turbolinks navigation: Parsed context is cached
only while its source element and JSON text remain unchanged. Replacing the context element or
morphing its payload in place now makes the next core render or immediate Pro hydration receive the
new page's URL, locale, and application-specific values instead of the first page's context. Fixes
#4583.
#4765 by
ihabadham. -
Stopped replacing Shakapacker-owned watch binstubs during installation: React on Rails no longer ships
its ownbin/shakapacker-watchtemplate. Generated Procfiles use Shakapacker's optional watch binstub when
it is present and fall back tobin/shakapacker --watchfor older supported Shakapacker installations.
Existing Shakapacker-provided or customized watch binstubs remain untouched, including under--force. Fixes
#4617.
#4715 by
ihabadham. -
Server rendering no longer crashes on or corrupts lone UTF-16 surrogates: When the JavaScript
renderer emits a string containing a lone surrogate (commonly from truncating text mid-emoji, e.g. an
excerpt cut withslice/substring),JSON.stringifyserializes it as a\uXXXXescape that Ruby's
JSON.parsemishandles — depending on the case andjsongem version it raisesincomplete surrogate pair, returns invalid UTF-8, or silently mis-decodes the value. The parser now normalizes such escapes to
the Unicode replacement character (�) and passes the content through to the browser instead of failing.
This works the same acrossjsongem versions. Clean payloads are unaffected: repair only runs when the
text actually contains a surrogate escape, a single cheap substring check. Fixes
#4710.
#4745 by
AbanoubGhadban. -
[Pro] Streaming caches no longer persist error-containing renders: When a streamed render
emits a chunk withhasErrors: true(for example a Suspense boundary whose async data fetch hit a
transient failure on that one request), the resulting chunk set is no longer written to
Rails.cache. Under production defaults (raise_non_shell_server_rendering_errors: false), a stream
whose shell rendered but whose async boundary errored completes "normally", so both stream cache-write
paths (ReactOnRailsPro::StreamCachefor prerender caching andcached_stream_react_component's
view-level cache) previously persisted the broken fragment and served it from cache to every
subsequent visitor on that key until the entry expired — turning a single transient failure into a
persistent outage for that component. Both paths now detect the error chunk and skip the cache write;
clean renders are still cached. Fixes
#4581.
#4722 by
justin808. -
[Pro] Buffered and static RSC caches no longer persist error-containing renders: Buffered
component HTML and static RSC HTML now skip cache writes when any streamed chunk reports
hasErrors: true; clean renders remain cacheable. Fixes
#4723.
#4804 by
justin808. -
renderComponentnow handles DOM IDs containing selector metacharacters: Component payloads are
matched by their exactdata-dom-idattribute value, so valid IDs containing quotes, backslashes,
hashes, periods, or brackets no longer throw selector errors or fail to render. Fixes
#4585.
#4808 by
justin808. -
Stopped Doctor from warning when a layout uses only one pack helper:
rake react_on_rails:doctor
no longer emits⚠️ <layout>: has javascript_pack_tag but missing stylesheet_pack_tag(or its mirror)
based purely on helper asymmetry. A JavaScript-only Shakapacker entrypoint, a CSS-only pack, and a
hybrid layout that loads its CSS through Propshaftstylesheet_link_tagare all valid, so detected pack
helpers are now reported informationally instead of as warnings. A fresh create-react-on-rails Rails 8
RSC app no longer receives a spurious layout warning. Fixes
#4619.
#4724 by
justin808. -
[Pro] Stopped logging routine async-props stream-close races at error level: When a client
disconnects mid-render, or astream_react_component_with_async_propsblock keeps emitting after
renderCompletewinds the connection down, writes to the already-closed renderer request stream are
now treated as routine disconnects: logged once atdebug(no backtrace, gated onlogging_on_server)
and short-circuited so the block's remaining props are skipped silently. Previously every remaining
prop produced anerror-level entry with a 5-frame backtrace, so a single disconnect spammed logs in
proportion to the number of props still in flight and buried genuine emit failures in the noise.
Genuine write failures (for example aJSON::GeneratorError) still log aterrorwith a backtrace.
This mirrors the siblingReactOnRailsPro::Stream#log_client_disconnectconvention. Fixes
#4325.
#4719 by
justin808. -
[Pro] Stopped retaining both raw and parsed source maps per pooled Node renderer VM: Once a
bundle's source map is parsed on first use, the raw map JSON (up to 50MB under the cap) is released and
the parsed map becomes the VM generation's single retained copy. For inline (data:) maps the encoded
URL payload (larger than the raw JSON) is released as well, so the single-copy reduction applies to both
external and inline maps. A registration whose raw map was released never falls back to re-reading the
map from disk, preserving the same-path rebuild guarantee: old VM generations keep remapping through
their own map even after the on-disk map is overwritten. Also makes an unusable inline (data:) source
map terminal on the lazy lookup path instead of retried, matching registration-time behavior. Partially
addresses
#4313.
#4711 by
justin808. -
[Pro] Made Node renderer cache identities cover complete artifacts and hardened multi-origin rolling deploys:
Renderer IDs now bind the server/RSC role, bundle bytes, and the final companion-file names and bytes, so
manifest-only andloadable-stats.jsonchanges invalidate fragment, upload, pre-seed, and runtime caches together.
The built-in HTTP adapter accepts ordered plural previous URLs, normalizes bare origins through the configured mount,
preserves explicit paths, rejects ambiguous legacy hashes across origins, verifies every v2 payload before staging,
and enforces monotonic operation deadlines. Fixes
#4620.
#4701 by
justin808. -
[Pro] Capped the size of external source maps read by the Node renderer: External
.mapfiles
whose on-disk size exceeds 50MB are skipped by a pre-read size gate rather than read into memory (this is
not a hard memory bound: a map that grows between the size check and the read is still read in full).
Previously only inline (data:) source maps were
size-capped, so a large external map was read and retained for the life of each pooled VM. Behavior change:
stack frames for a bundle whose external map exceeds the cap keep their bundled locations instead of being
remapped to original sources, and the renderer logs a warning naming the map. An oversized map is never
substituted: the renderer will not fall back to a differently-named map alongside the bundle. When the
oversized map is the one the bundle names, that result is final for the VM generation and is not retried;
a map that merely arrives late is still retried as before. The 50MB limit matches the pre-existing cap for
inline maps. Partially addresses
#4313.
#4688 by
AbanoubGhadban. -
[Pro] Bounded RSC browser performance fallback marks across soft navigations: Browsers
that cannot preservePerformanceMark.detailnow retain only the 200 most recent fallback
entries, and React on Rails Pro clears the fallback queue during Turbo/Turbolinks page teardown.
The TypeScript runtime, generated inline helper, and Ruby streaming mirror share the same limit.
Fixes #4329.
#4690 by
justin808. -
Redacted sensitive server-render error context and tightened Ruby request helpers: Server-render
exceptions no longer retain raw component props, generated JavaScript, or renderer parse details in log and
error-tracker context.
Development service checks now use safe YAML loading, and Pro renderer asset queries encode filenames.
Fixes #4597.
#4624 by
justin808. -
[Pro] RSC render errors no longer leak server details into client DOM: In production, the
REACT_ON_RAILS_RSC_ERRORSinline script now emits only{ hasErrors: true }— the full error
message, stack trace, and file paths are redacted. Uses an allowlist (development/test show full
diagnostics) so custom envs like staging default to redacted. RSC rendering errors are now also
reported to error reporters (Sentry/Honeybadger) even with the defaultthrowJsErrors: false
config, via a custom'renderingError'stream event. Fixes
#4629.
#4631 by
AbanoubGhadban. -
[Pro] Hardened Node renderer authentication and multipart uploads: Authenticated multipart
clients must now send the password field before file parts; the renderer rejects and discards leading file
parts before creating upload storage. Uploads now enforce a 100 MB total request limit across files, fields,
and multipart framing, plus a 1,000-part cap and a matching per-file cap. Password comparisons use fixed-length
digests to avoid leaking the configured password length. Fixes
#4596.
#4623 by
justin808. -
[Pro] RSC migration diagnostics require graph-derived client references: The RSC generator's
doctor now reports custom or legacyclientReferencesvalues as missing the generated
manifest-backed resolver, and the migration guide shows how to print the canonical CommonJS
resolver and run it in custom ESM webpack configs with a tested compatibility prelude. Fixes
#4609.
#4625 by
justin808. -
[Pro] Streamed RSC roots hydrate without transport-node mismatches: Pro client hydration now
removes the embedded RSC payload initializer from the hydration root, relocates leading streamed
RSC resource tags out of the root before React attaches, and wraps the default RSC provider path in
the same null Suspense boundary used by the server stream. This prevents recoverable hydration
mismatches on streamed RSC apps such as the flagship demo. Fixes
#4525. #4532 by justin808. -
[Pro] Healthy incremental streams no longer log premature close-hook timeout warnings: The
Pro Node renderer now suppresses the short close-hook timeout warning while an incremental response
is still open and finishing normally, while preserving the diagnostic when the close hook remains
stalled after the response finishes. Fixes
#4524.
#4531 by
justin808.
Added
-
[Pro] Node Renderer OpenTelemetry initialization now composes with application observability stacks:
Applications can append custom instrumentations to the built-in HTTP and Fastify pair, merge resource-detector
attributes below explicit resource configuration, or opt in to preserving rendererror.*spans through an
application-owned global provider. Empty service-name values from environment variables, options, and resource
attributes are treated as unset. Renderer-managed shutdown disables registered instrumentations and shuts down
provider components after successful initialization, while failed initialization preserves caller-supplied processors
and exporters.
Fixes #4867.
#4878 by
sashakhar1. -
[Pro] HTTP/1.1 is now a supported Node Renderer transport for load balancers and HTTP/1.1-only probes:
Rails can opt out of forcing h2c for cleartext renderer URLs with
config.renderer_http_force_http2 = false, paired withfastifyServerOptions: { http2: false }on the Node
Renderer. Regular rendering and response streaming continue to work. Async props require full-duplex behavior across
every hop, so request-buffering or half-duplex HTTP/1.1 intermediaries remain unsupported. Fixes
#4868.
#4887 by
sashakhar1. -
Version-matched agent skills and bundled docs: The
react_on_railsgem and
react-on-railsnpm package now ship install/upgrade, React Server Components adoption,
streaming debugging, and doctor fix-loop skills with concise package-local references.
Newly scaffoldedAGENTS.mdfiles point agents to the installed package content before
the hosted docs, avoiding guidance drift across releases. Fixes
#4605.
#4809 by
justin808. -
[Pro] RSC agent guardrails installer: New
rake react_on_rails:install_rsc_agent_guardrailstask (also run automatically by the RSC
generator) installs a Claude Codersc-app-safetyskill and an advisory hook into a host app's
.claude/, steering AI coding agents away from React Server Components API footguns (mounting the
RSC payload route without authentication, trusting server-component props, exposing the Node
renderer, leaking secrets). Idempotent and safe to re-run after upgrades.
#4606 by
justin808. -
Existing-app Pro choice: Interactive
react_on_rails:installruns now ask whether to enable
React on Rails Pro when no Pro, RSC, or standard-only choice is supplied. The bounded prompt defaults
to yes and explains trust-based evaluation and production licensing, while noninteractive runs retain
the open-source-only default. Explicit--pro,--no-pro,--rsc,--no-rsc, and--standard-only
choices skip the prompt. The doctor and post-install guidance now show the Pro upgrade path when Pro is
off. Fixes #4604 in
#4615 by
justin808. -
Agent-legible doctor contract:
bin/rails react_on_rails:doctor FORMAT=jsonnow includes stable
check IDs, explicit severity and documentation fields, a nullable field reserved for diagnosis-specific
safe mechanical fix commands,
and self-contained remediation prompts. The versioned schema, deterministic ordering, and exit-code
behavior are documented for CI and coding-agent integrations. Fixes
#4602.
#4611 by
justin808. -
[Pro] Optional authorization callback for RSC payload routes: Apps can configure
rsc_payload_authorizerto reject payload requests before component props are parsed or rendered.
Existing behavior remains unchanged when the callback is unset, and the RSC security docs now explain
the public endpoint boundary and app-controller authorization seam.
Fixes #4595.
#4621 by
justin808. -
[Pro] Configurable license-token secret sources: Rails applications can now provide a paid
license throughconfig.license_token, including from Rails credentials, while standalone Node
renderers can use thelicenseTokenoption. Explicit nonblank configuration takes precedence over
REACT_ON_RAILS_PRO_LICENSE, blank configuration preserves the environment fallback, and renderer
diagnostics mask token values. #4552 by
ihabadham.
Changed
- [Pro] Render requests now send raw JavaScript bodies to the Node renderer: Non-bundle render
requests use a rawapplication/vnd.react-on-rails.render-request+javascriptbody with metadata in
X-React-On-Rails-Pro-*headers instead ofapplication/x-www-form-urlencoded, removing
URL-encoding overhead on large rendering payloads. The renderer still accepts the legacy form
encoding, so a not-yet-upgraded gem keeps working against an upgraded renderer during rolling
deploys; deploy the Node renderer before or together with the gem upgrade, since an older renderer
rejects the new content type. Fixes
#3584.
#4579 by
alexeyr-ci2.