Added
- [Pro] Bidirectional async props streaming (pull mode):
stream_react_component_with_async_propscan now let React request lazy props during incremental rendering, complementing the existing eager push model. The stream protocol carriespropRequest/renderCompletecontrol messages from the node renderer back to Rails,AsyncPropsManagercan request or reject props on demand, and the Pro dummy app now covers pure pull, mixed push/pull, Redis-backed fixtures, and rejection/error-boundary scenarios. Closes Issue 4046. PR 4048. - Owner Stacks in development error reports: When a React component throws during rendering, React on Rails now enriches its development error reporting with React 19.1+'s dev-only
captureOwnerStackoutput — the chain of components that rendered the failing one (e.g.at Avatar/at PostCard/at PostList). On the server-side rendering path, the owner stack is captured synchronously inside the Pro streamingonErrorcallback and appended to the error's stack, so it flows through to the Rails-sideReactOnRails::PrerenderError/SmartError(:server_rendering_error) output and the streamed shell-error HTML. On the client, recoverable hydration mismatches (onRecoverableError) automatically gain an owner-stack line in the branded development log, and apps that register their ownonCaughtError/onUncaughtErrorhandler get an additive[ReactOnRails] Render error ... Owner stack:line for client render errors. To avoid displacing React's own built-in development diagnostics (component stacks, error-boundary hints), React on Rails does not auto-attach caught/uncaught handlers solely to log owner stacks. Owner-stack capture requires React >= 19.1 running its development build, so it is a strict no-op on older React and in all production builds (no capture, nocaptureOwnerStackcall), asserted by tests. The ExecJS rendering path is out of scope (capture must happen JS-side, synchronously, inside React's error callback). Closes Issue 3887. PR 4089 by justin808. - hydrate_on scheduling:
react_componentnow accepts ahydrate_on:option to defer client hydration of an island until it is needed —:immediate(default, unchanged),:visible(hydrate when the container scrolls near the viewport viaIntersectionObserver), or:idle(hydrate during browser idle time viarequestIdleCallback). Deferred roots are cleaned up on Turbo/Turbolinks navigation and re-scheduled if their node is detached and reattached; unsupported modes raise, and non-:immediatemodes are rejected when React on Rails Pro is installed. Closes Issue 3890. PR 4037 by justin808.
Changed
- Breaking (types only):
RenderFunctionno longer accepts the legacy 3-argument renderer shape: The exportedRenderFunctiontype is now exactly the 2-argument server/client render-function form ((props, railsContext) => RenderFunctionResult), equal to the existingServerRenderFunction. It previously also accepted a 3-argument(props, railsContext, domNodeId) => RenderFunctionResultarm, which let nonsensical role combinations typecheck (a server render-function "returning" a renderer teardown, or a renderer "returning" a server-render hash). Renderer functions — the 3-argument form that owns its own DOM mount and may return a{ teardown }wrapper — should now be typedRendererFunction.ReactComponentOrRenderFunctionalready includesRendererFunction, so renderer-shaped functions remain registerable. The tighter type also drops the re-narrowingascasts the unified type forced increateReactOutputand the Pro tanstack-router render function. This is a compile-time-only change with no runtime behavior difference; only TypeScript consumers that annotated a 3-argument renderer asRenderFunctionneed to switch it toRendererFunction. Closes Issue 3592. PR 4096 by justin808. - [Pro] Pinned
react-on-rails-rscto the stable19.0.5release: The generator default, the root and Pro package manifests, the lockfile, and the Pro RSC install docs now pin the stablereact-on-rails-rsc@19.0.5(previously the19.0.5-rc.7prerelease). The native RSC CSS FOUC fix requiresreact-on-rails-rsc >= 19.0.5. Closes Issue 3634. PR 4080 by justin808. - [Pro] RSC peer-compatibility warn-tier floor raised to stable
19.0.5: The Pro node renderer'srecommendedMinforreact-on-rails-rscis now the published stable19.0.5(previously the dormant19.0.2). Anyone still on an older 19.x build (19.0.2–19.0.4) now gets a loud startup warning that they are missing the coordinated RSC fixes shipped in19.0.5(FOUC stylesheet preloading, async manifest signatures);19.0.5+ no longer warns. Refs Issue 3632. PR 4078 by justin808. - [Pro] RSC peer wildcard replaced with an explicit range: The Pro npm package's optional
react-on-rails-rscpeer dependency is now^19.0.5(>= 19.0.5 < 20.0.0) instead of"*", so installs are floored at the RSC CSS FOUC fix release19.0.5and capped below the next major20.0.0, rather than"*"accepting any version including pre-FOUC builds and an unknown future major. Within the19.xline, per-version compatibility is enforced by the Pro node renderer's runtime version check (rscPeerSupport.ts), not by this advisory peer range. Fixes Issue 3965. PR 4082 by justin808.
Fixed
-
Deferred hydration error reporting handles non-Error thrown values: Delayed
hydrate_onrenders now normalize strings,null, and frozenErrorinstances before logging, so reporting the failure does not throw again or mutate user errors. PR 4120 by ihabadham. -
Explicit Webpack installs now pass the resolved bundler to Shakapacker.
rails generate react_on_rails:install --no-rspackand--webpacknow setSHAKAPACKER_ASSETS_BUNDLER=webpackbefore runningshakapacker:install, so Shakapacker installs Webpack dependencies instead of falling back to its default bundler. Fixes Issue 4108. PR 4109 by ihabadham. -
Generated demo paths now honor custom Shakapacker source roots. The install generator resolves demo components, entrypoints, stylesheets, TypeScript includes, Tailwind imports, and RSC hints from the app's Shakapacker
source_path/source_entry_pathsettings, including slash entry roots, while wrapping long source hints in the generated demo views. Fixes Issue 4062. PR 4107 and PR 4130 by justin808. -
RSC-safe generated i18n locale defaults: The JavaScript locale compiler that generates
default.jsno longer importsreact-intlor wraps messages indefineMessages; it now emits the message descriptor object directly. This lets the generated locale defaults be imported from React Server Component bundles without pulling in the client-orientedreact-intlentrypoint, and without raising the minimum supportedreact-intlversion. The exporteddefaultMessagesshape is unchanged, and existing apps regenerate automatically because the compiler treats adefault.jsstill using the olddefineMessagestemplate as stale. Fixes Issue 4132. PR 4146 by justin808. -
Abort the in-flight SSR render when the client disconnects (Pro streaming): Previously, when an HTTP client disconnected (or a request timed out) mid-stream, the Node renderer kept driving the React render to completion against a consumer that was already gone — wasting CPU and, for RSC/
cache()-wrapped data fetches, continuing to hit the app's database/APIs. The Pro streaming layer now propagates the consumer-side teardown upstream into ReactDOM'sPipeableStream.abort(): when the renderer worker detects the client disconnect it destroys the render's output stream, which aborts the in-flight render and releases the request's RSC payload streams. Normal completion is unaffected (the abort only fires when the output is destroyed before it ends, and never when a render error closes the stream). This also establishes the precondition for React 19.2'scacheSignal, which React settles automatically once a render is aborted (thecacheSignal-specific test and docs remain a follow-up). Part of Issue 3885. PR 4093 by justin808. -
[Pro] Bounded the RSCProvider RSC payload cache to prevent unbounded growth under high-cardinality props: The provider-scoped promise cache (
fetchRSCPromisesRef) and its companion bookkeeping (lastSuccessfulRSCPromisesRef, refetch versions, and theversions/successfulVersionsstate maps) are now backed by a bounded LRU (default cap 50 distinct RSC payload keys). High-cardinalitycomponentProps(e.g. per-row or per-search-query routes) previously grew these maps without limit for the provider's entire lifetime — a latent memory leak. Eviction only affects cold, least-recently-used keys beyond the cap; same-key cache hits, refetch,recoverOnErrorrestore, and version bumping are unchanged, and an in-flight refetch's key is pinned (with ref-counted pins, so overlapping same-key refetches stay protected until all of them settle) and cannot be evicted out from under its restore path. The per-keyuseSyncExternalStoresubscription/fan-out optimization from the same issue is intentionally deferred pending profiling. Refs Issue 3564. PR 4097 by justin808. -
[Pro] Enrich deferred-render RSC errors with the bundle diagnostic: When a Server Component failed during React's deferred render phase (a Suspense boundary resolving a lazy RSC element), the error surfaced through
renderToPipeableStream'sonErroras a generic React stream error — the original RSC bundle diagnostic (the real server-side error message and module path) was already out of scope and lost. The Pro streaming layer now threads the captured diagnostic through the request-scoped tracker and merges it into the surfaced error, soReactOnRails::PrerenderError/SmartErroroutput names the failing RSC component and module instead of a bare React message. Since React'sonErrorcarries no component key, attribution is conservative: one captured diagnostic is merged exactly, two or more produce a combined "one of these N RSC components failed" message (never a single false pinpoint), and each captured diagnostic is consumed on first use so an unrelated later failure in the same render is never mislabeled. Completes the deferred-render half of the bundle-diagnostic work (the fetch and preloaded-hydration halves shipped earlier). Closes Issue 3475. PR 4100 by justin808.