github shakacode/react_on_rails v17.0.0

5 hours ago

Breaking Changes

  • [Pro] Removed the undocumented ReactOnRailsPro::Cache.fetch_react_component class API:
    Pro apps should use the supported cached helper APIs (cached_react_component,
    cached_react_component_hash, and related helpers) instead of calling the low-level cache class
    directly. The helper cache path still preserves generated-pack loading on cache hits, tag
    registration, and expires_at handling internally. Fixes
    #4497.
    #4541 by
    justin808.
  • [Pro] React Server Components now require the stable React 19.2.x RSC line: React on Rails Pro 17 requires react-on-rails-rsc >= 19.2.1 < 20, React >= 19.2.7, and matching React DOM. The Pro generator scaffolds that coordinated stable runtime and rejects prerelease RSC packages below the stable peer floor. The adopted react-on-rails-rsc artifact uses the React on Rails Pro commercial terms rather than MIT and reports SEE LICENSE IN LICENSE.md in npm metadata. Set REACT_ON_RAILS_PRO_DISABLE_VERSION_CHECK=1 only as an emergency rollout escape hatch to downgrade startup errors to warnings. #4357. #4490 and #4670 by justin808.
  • Removed the inert config.server_render_method option: The open-source configuration no longer accepts config.server_render_method. The option never selected a server render method — the open-source gem always renders with ExecJS — and its validator raised ReactOnRails::Error at boot for any value other than blank or "ExecJS". Setting it now raises NoMethodError at boot, so delete any config.server_render_method = ... line from config/initializers/react_on_rails.rb; rake react_on_rails:doctor also flags the stale line. For a standalone Node rendering process, use React on Rails Pro's Node renderer, configured via ReactOnRailsPro.configure. Fixes #4415. #4423 by justin808.
  • Removed three deprecated configuration options (config.generated_assets_dirs, config.skip_display_none, config.defer_generated_component_packs): These were deprecated in v16 and are gone in v17. Setting any of them now raises NoMethodError at boot; delete the stale lines from config/initializers/react_on_rails.rb (rake react_on_rails:doctor flags them). Migration: delete config.generated_assets_dirs — public asset paths come from public_output_path in config/shakapacker.yml; delete config.skip_display_none — it had no runtime effect; replace config.defer_generated_component_packs = true with config.generated_component_packs_loading_strategy = :defer, and simply delete config.defer_generated_component_packs = false (the removed option was truthy-gated — only = true set :defer; = false was a no-op that fell through to the default strategy, so it did not mean :sync; set :sync explicitly only if you relied on synchronous loading). The default strategy is :async for Pro or :defer for non-Pro on Shakapacker 8.2.0+, and :sync on older Shakapacker. Fixes #4419. #4432 by justin808.
  • Removed the never-wired RenderRequest / JsCodeBuilder / RenderingStrategy rendering layer: The internal strategy-pattern classes ReactOnRails::RenderRequest, ReactOnRails::JsCodeBuilder, ReactOnRails::RenderingStrategy (with ExecJsStrategy), and — in Pro — ReactOnRailsPro::JsCodeBuilder and ReactOnRailsPro::RenderingStrategy::NodeStrategy, plus the undocumented ReactOnRails.rendering_strategy and ReactOnRails.js_code_builder module accessors, are removed. This scaffolding was built for the strategy-pattern refactor in #2905 (closed without wiring it in) and was never invoked on any production server-rendering path — SSR runs through ServerRenderingJsCode and ServerRenderingPool, which never touched this layer. These constants and accessors were internal and undocumented; if you reference them in application code, remove the reference (the layer performed no work). Fixes #4414. #4437 by justin808.
  • Ruby 3.3+ is required for React on Rails v17: The open-source gem now requires Ruby >= 3.3.0, aligning it with React on Rails Pro, create-react-on-rails-app, and the CI minimum matrix. React on Rails v16 remains the upgrade path for applications that must stay on Ruby 3.2 or older. #3500 by justin808.
  • [Pro] Node Renderer now requires Ruby 3.3+ for the async-http transport: The react-on-rails-pro gem now requires Ruby >= 3.3 (raised from >= 3.0) because async-http depends on Ruby 3.3 features. Upgrade Ruby before moving to this release. See docs/pro/updating.md for the full upgrade guide. #3320 by AbanoubGhadban.
  • [Pro] config.renderer_http_pool_size now limits async-http connections per renderer client: Existing numeric values now cap concurrent async-http connections for each renderer client instead of sizing a persistent process-wide connection pool. HTTP/2 may multiplex request streams over those pooled connections. Setting nil keeps the default connection limit and does not make the async-http client unlimited. Persistent connection reuse is automatic when a long-lived Fiber.scheduler is present. See docs/pro/updating.md for the full upgrade guide. #3320 by AbanoubGhadban.

Added

  • [Pro] React 18 support for non-RSC streaming SSR: stream_react_component with synchronous
    props is now explicitly supported on React 18 as well as React 19. Permanent packed-artifact
    coverage verifies a production Webpack build and progressive Suspense output on React 18 without
    installing or bundling react-on-rails-rsc; async props and React Server Components remain React
    19-only. Fixes #4642.
    #4658 by
    justin808.

  • Generated Rails response TypeScript contracts: Rails apps can now register explicit JSON response
    contracts with ReactOnRails::TypeScriptResponseTypes and run
    rake react_on_rails:generate_response_types to emit importable .d.ts declarations plus a
    RailsResponseTypes lookup map for TanStack Query clients. Fixes
    #4247. #4259 by justin808.

  • Typed Rails action callers for TanStack Query mutations: The react-on-rails/railsAction
    subpath now exports createRailsAction, a same-origin JSON caller that attaches Rails CSRF headers and
    lets mutation code type responses with the generated RailsResponseType<'controller.action'> lookup.
    Fixes #4248. #4260 by justin808.

  • [Pro] Typed Rails action callers for TanStack Query mutations: The Pro package mirrors the
    createRailsAction helper at react-on-rails-pro/railsAction. #4260 by justin808.

  • [Pro] Loader-time RSC prefetch through the provider cache: Client-router loaders can now
    call prefetchServerComponent to warm a bounded page-global prefetch store that RSCProvider
    adopts on the next RSCRoute render. Prefetches no-op when the SSR payload is already embedded,
    support loader abort signals, and resolve without unhandled rejections after fetch/decode failure
    self-eviction. Fixes #4460.
    #4489 by
    justin808.

  • bin/dev clean clears generated bundles and caches: The command stops development processes, reads config/shakapacker.yml or SHAKAPACKER_CONFIG, removes configured Shakapacker public/private output and cache paths plus common Rails, JavaScript, and renderer bundle caches, and skips unsafe paths outside the app root. #4218 by justin808.

  • [Pro] Buffered RSC rendering for static pages:
    buffered_stream_react_component and cached_buffered_stream_react_component
    now render server components through the Pro streaming/RSC renderer while
    returning complete HTML to Rails, so static or cacheable pages can avoid
    ActionController::Live response commits when progressive streaming is not
    needed. When RSC support is enabled, prerendered Pro fragment cache keys now
    include the RSC bundle digest, or a missing-bundle sentinel until that bundle
    exists, so deploys that update only the RSC bundle invalidate cached RSC output
    consistently. Fixes
    #4263.
    #4268 by
    justin808.

  • [Pro] Cached static RSC public-page helper and diagnostics:
    cached_static_rsc_component caches stripped static RSC HTML for public pages
    that intentionally skip the generated page pack, while respecting
    auto_load_bundle: false and preserving explicit sidecar assets. Static RSC
    render diagnostics report cache hit/miss state, redacted cache-key digests,
    HTML and stripped payload bytes, emitted asset bytes, and RSC client-reference
    entries for performance investigations. Fixes
    #4295 and
    #4296.
    #4386 by
    justin808.

  • [Pro] Opt-in browser performance marks for streamed RSC observability: Pro streaming can now emit inline browser marks for RSC stream completion, embedded Flight payload chunks, Node-side flushes, hydration start, and first interactive client effects, with byte counts and timing details that avoid serialized props or payload contents. The documented path uses body-delivered marks and a fallback queue instead of HTTP trailers, so apps can measure streamed RSC responses across CDN paths that may strip or hide trailer timing. Fixes #4205, #4206, and #4207. #4222 by justin808.

  • [Pro] Server-Timing attribution for streamed RSC responses: When rsc_stream_observability: true, the streamed RSC response now also carries a Server-Timing response header with a ror_stream_shell metric (Rails shell render, including the blocking wait for each component's first renderer chunk), set in the narrow window before ActionController::Live commits headers and appended to any existing Server-Timing entries. The Node renderer additionally emits a ror_renderer_prepare metric (execution-context build plus render start) on its HTTP response. This is the server/renderer-side complement to the browser performance marks above, letting a reviewer attribute the streamed responseEnd tail to a specific phase rather than guessing. Total/stream-complete time stays on the react-on-rails:rsc:stream mark because ActionController::Live does not support HTTP trailers. Closes #4239. #4251 by justin808.

  • [Pro] Focused RSC doctor artifact diagnostics: rake react_on_rails:doctor:rsc and
    Doctor.new(only: ...) now run the RSC artifact checks directly, reporting missing,
    stale, invalid, or dev-server-backed RSC bundle and manifest files with rebuild guidance
    while explaining when Pro path resolution is unavailable. Closes
    #4204.
    #4223 by
    justin808.

  • [Pro] Bidirectional async props streaming (pull mode): stream_react_component_with_async_props can now let React request lazy props during incremental rendering, complementing the existing eager push model. The stream protocol carries propRequest / renderComplete control messages from the node renderer back to Rails, AsyncPropsManager can 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 #4046. #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 captureOwnerStack output — 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 streaming onError callback and appended to the error's stack, so it flows through to the Rails-side ReactOnRails::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 own onCaughtError/onUncaughtError handler 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, no captureOwnerStack call), asserted by tests. The ExecJS rendering path is out of scope (capture must happen JS-side, synchronously, inside React's error callback). Closes #3887. #4089 by justin808.

  • hydrate_on scheduling: react_component now accepts a hydrate_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 via IntersectionObserver), or :idle (hydrate during browser idle time via requestIdleCallback). Deferred roots are cleaned up on Turbo/Turbolinks navigation and re-scheduled if their node is detached and reattached; unsupported modes raise, and non-:immediate modes are rejected when React on Rails Pro is installed. Closes #3890. #4037 by justin808.

  • [Pro] Tag-based cache revalidation (a Next.js revalidateTag analog): The fragment-caching helpers (cached_react_component, cached_react_component_hash, cached_stream_react_component, cached_async_react_component) now accept an optional cache_tags: option (String, Proc, any object responding to cache_key such as an ActiveRecord model, or an Array of any mix), and the new ReactOnRailsPro.revalidate_tag(tag) / revalidate_tags(*tags) API deletes every cached entry registered under a tag via a Rails.cache-backed tag->key index. A new ReactOnRailsPro::Cache::Revalidates ActiveRecord concern (revalidates_react_cache) drives revalidation from after_commit, so the model that owns the data also owns cache invalidation (and composes with touch:). Revalidation is best-effort with correctness bounded by expires_in (a development-mode warning fires when cache_tags: is used without it); index growth is bounded by the new config.cache_tag_index_expires_in (default 7 days) and config.cache_tag_index_max_keys (default 5,000) settings. Existing cache_key:-only behavior is unchanged. Closes #3871. #3964 by justin808.

  • React 19 root error callbacks: ReactOnRails.setOptions({ rootErrorHandlers: { onRecoverableError, onCaughtError, onUncaughtError } }) registers React's root error callbacks globally; React on Rails applies them to every hydrateRoot/createRoot call it makes and invokes them with an extra context argument whose componentName and domNodeId fields are optional. In development, recoverable hydration errors now log an actionable React on Rails message (component name, dom id, component stack, and a link to the new Debugging Hydration Mismatches guide) alongside React's default error reporting, which stays intact so window-'error'-based tooling keeps working. Partial rootErrorHandlers updates merge per key, so registering one callback later does not drop the others. On React <19 (and <18 for onRecoverableError), React on Rails retains registrations for future upgrades, but the current runtime cannot invoke unsupported callbacks and logs a one-time console warning. On React on Rails Pro RSC/streaming hydration paths, user callbacks chain with (never replace) Pro's internal recoverable-error handler. Addresses #3892. #3933 by justin808.

  • useRailsForm hook + render_model_errors controller concern (an Inertia useForm-style bridge to Rails controllers): New React hook useRailsForm (importable from react-on-rails/useRailsForm) makes posting a React form to a plain Rails controller turnkey: data/setData, per-field errors, processing, wasSuccessful, submit verbs (post/put/patch/delete/submit), reset/clearErrors/setError, automatic CSRF attachment from the Rails csrf-token meta tag, JSON request/response handling, and mapping of 422 + { errors: { field: ["message"] } } responses onto per-field error state. Success results surface a redirectTo target (followed-redirect URL or JSON redirect_to hint) without navigating, forward-compatible with the client-routing work in #3873. The gem side adds the opt-in ReactOnRails::Controller::FormResponders concern whose render_model_errors(record) renders ActiveModel errors in exactly that shape, so validations stay in the model with no API layer and no client-side duplication. Includes a new Forms and Mutations docs page (with an Inertia useForm mapping table and a Server Functions #3867 cross-link) and a runnable dummy-app example (/rails_form). v1 is fetch-only; transform, recentlySuccessful, and file-upload progress are deferred. Closes #3872. #3942 by justin808.

  • [Pro] Built-in node renderer /health and /ready probe endpoints: The node renderer can now register first-class liveness (GET /health -> 200 with a status-only body) and readiness (GET /ready -> 503 until the answering worker is online and has at least one server bundle compiled, then 200) endpoints, replacing the hand-rolled configureFastify health-check recipe for the common case. The endpoints are off by default and enabled with the new enableHealthEndpoints config option (or RENDERER_ENABLE_HEALTH_ENDPOINTS=true, TRUE, yes, YES, or 1); they are unauthenticated like /info but expose no runtime version or path details. The 1 alias is scoped to RENDERER_ENABLE_HEALTH_ENDPOINTS so existing node-renderer boolean environment flags keep their previous parsing behavior. Includes a new Health and Readiness Endpoints docs page with working Kubernetes (tcpSocket + exec with curl --http2-prior-knowledge -- the h2c listener cannot be probed with HTTP/1.1 httpGet), ECS, and Docker Compose probe examples. Closes #3880. #3939 by justin808.

  • [Pro] Source-mapped stack traces in the Node renderer: SSR errors now point at the original TypeScript/JavaScript file:line:column instead of bundled positions. When the server bundle carries an inline source map (or a .map file is available next to the uploaded bundle), the renderer captures the map text for the VM's bundle generation and lazily parses it on the first error before remapping stack frames — both for exceptions returned to Rails as renderer errors and for stacks captured inside the bundle that surface through ReactOnRails::PrerenderError. Bundles are also now evaluated with their real file path, so even unmapped stacks name the bundle file rather than evalmachine.<anonymous>. No per-request overhead: map parsing and frame remapping happen only when an error's stack is accessed, and parsed maps are cached per bundle generation. Uses Node's built-in module.SourceMap (no new dependencies). Part of #3893. #3940 by justin808.

  • Machine-readable doctor output (FORMAT=json): bin/rails react_on_rails:doctor FORMAT=json (and ReactOnRails::Doctor.new(format: :json)) now emits a stable, versioned JSON report — schema_version, ror_version, overall status, per-check entries with stable snake_case ids (pass/warn/fail status, most-severe message, full details), and a summary of check counts — so coding agents and tooling can consume diagnosis results without parsing the human-formatted text. Stray check output is routed to stderr so stdout stays valid JSON; the default human-readable output and exit-code semantics are unchanged. Split out from the MCP-server RFC in #3870. Closes #3943. #3948 by justin808.

  • Consumer-facing AI-agent guidance scaffolded into generated and installed apps: rails generate react_on_rails:install (and therefore create-react-on-rails-app, which delegates to it) now writes a concise, app-scoped AGENTS.md plus thin editor-pointer files (CLAUDE.md, .cursor/rules/react-on-rails.mdc, .github/copilot-instructions.md) so an AI coding agent dropped into a fresh app already knows how to register a component, use the react_component view helper, choose .client/.server bundles, recover from the top runtime errors (sourced from SmartError), and run bin/rails react_on_rails:doctor. The errors section tracks the live SmartError messages, and the editor files are pointers (not copies). Emission is gated by --agent-files/--no-agent-files (default on) in both paths and never overwrites an existing file. Cross-links the eval-harness work in #3832. Closes #3868. #3924 by justin808.

  • First-party font optimization helper (a next/font/local analog): New ReactOnRails::FontHelper#react_on_rails_font_face view helper returns <head> markup for a committed, self-hosted .woff2 font: a <link rel="preload" as="font" type="font/woff2" crossorigin>, an @font-face with font-display: swap, and an optional metric-matched fallback @font-face (size-adjust plus ascent-override / descent-override / line-gap-override) so the system fallback occupies the same space as the web font and the swap produces no layout shift (CLS). Self-hosting through the asset pipeline avoids any third-party font-host request. Includes a new Font Optimization docs page (self-hosting, preload, font-display, the size-adjust fallback technique with a worked Inter-over-Arial derivation, subsetting guidance, and a CLS note) and a runnable dummy-app example. v1 covers the next/font/local committed-file path and the non-streaming react_component_hash head-injection path. Closes #3875 (partial). Deferred sub-tasks tracked in #3921. #3923 by justin808.

  • Stable SmartError codes and generated error reference: SmartError messages now include stable ROR### codes and canonical documentation URLs, and docs/oss/reference/error-reference.md is generated from the SmartError definitions with a drift check. Fixes #3894. #3936 by justin808.

  • Preload link helper for generated component packs: Added react_on_rails_preload_links so layouts can emit preload, modulepreload, and stylesheet preload tags for auto-bundled component packs from the Shakapacker manifest. Fixes #3889. #3935 by justin808.

  • Tailwind CSS v4 generator option: rails generate react_on_rails:install --tailwind and create-react-on-rails-app --tailwind now install Tailwind CSS v4, wire @tailwindcss/postcss for Webpack or Rspack, and style the generated server-rendered HelloWorld example with extracted component CSS support. Interactive create-react-on-rails-app runs recommend Tailwind by default while still allowing users to opt out. Fixes #3895. #3937 by justin808.

  • [Pro] RSC registration entry path override: Generated RSC precompile hooks, discovery builds, and client-reference stale checks now honor REACT_ON_RAILS_RSC_REGISTRATION_ENTRY_PATH so apps that write server-component-registration-entry.js outside the default generated path can point React on Rails Pro at the exact entry while retaining the existing fallback scan and stale-manifest cleanup. Fixes #3621. #3712 by justin808.

  • [Pro] RSC unstable_cache with Redis L2 and tiered caching: Added unstable_cache for RSC rendering with deterministic cache-key serialization (React flight protocol encoding with sorted object keys), a RedisCacheHandler for cross-worker L2 caching, a TieredCacheHandler for L1/L2 composition with configurable L1 TTL caps, and per-worker single-flight render coalescing. Resolves #3702. #3705.

  • [Pro] RSC manifest client reference discovery during precompile: Generated RSC Webpack configs now run RSCReferenceDiscoveryPlugin through the Shakapacker precompile hook to emit rsc-client-references.json, use that manifest for RSC client-reference bundling, and warn when the selected manifest is stale. #3556 by ihabadham.

  • [Pro] Imperative refetch API for <RSCRoute>: <RSCRoute> now accepts an optional ref typed as RSCRouteHandle, exposing refetch() so a parent or sibling can refetch a server component without knowing its componentName or componentProps. A new useCurrentRSCRoute() hook returns the same handle for client components rendered inside the RSC subtree (for example, an inline "Refresh" button rendered by the server component itself); calling it outside an <RSCRoute> ancestor throws useCurrentRSCRoute must be used inside an <RSCRoute>. Both APIs auto-update the rendered tree with no caller-side setKey/useState workaround and propagate to every <RSCRoute> instance bound to the same cache key. The internal cache invalidation runs inside a React transition, so old content stays visible while the new RSC payload streams in without a Suspense-fallback flash. The existing useRSC().refetchComponent(name, props) API and the ServerComponentFetchError-based retry flow are unchanged. Fixes #3106. #3552 by AbanoubGhadban.

  • [Pro] <RSCRoute ssr={false}> defers initial RSC payload generation: <RSCRoute> now accepts ssr={false} to skip server-side RSC payload generation for that route — the server streams the nearest <Suspense> fallback and the client fetches the payload through the existing RSCProvider path (cache lookup, /rsc_payload/:componentName fetch, ServerComponentFetchError, and useRSC().refetchComponent(...) retry). ssr defaults to true, so existing routes are unchanged and a mixed page can server-render some routes while deferring others. Deferred roots that do not manually call wrapServerComponentRenderer are now supported automatically: RSC-enabled generated client packs register a default RSC provider (also exported as react-on-rails-pro/registerDefaultRSCProvider/client for manual entrypoints) that wraps auto-bundled react_component(..., prerender: false) and deferred-only stream_react_component roots. Completes #3101. #3318, #3394 by ihabadham.

  • Ruby 4.0 CI support: Updated OSS latest-runtime CI coverage, local CI switching guidance, and public compatibility docs to test Ruby 4.0 while keeping Ruby 3.3 as the minimum supported CI lane. #3529 by justin808.

  • [Pro] HTTP rolling-deploy endpoint auto-mount: Configuring config.rolling_deploy_adapter = ReactOnRailsPro::RollingDeployAdapters::Http now automatically mounts ReactOnRailsPro::RollingDeploy::BundlesController at config.rolling_deploy_mount_path (default /react_on_rails_pro/rolling_deploy). Set the mount path to nil or blank to opt out and keep a manual draw_routes mount; apps that previously mounted the default route manually should remove that route or give secondary manual mounts a distinct as_prefix: to avoid duplicate named-route errors. Fixes #3476. #3504 by justin808.

  • [Pro] unstable_cache for React Server Component fragment caching: New experimental unstable_cache(fn, options) wrapper memoizes a server component's serialized RSC payload — replaying the stored bytes on a cache hit and tee-ing output to both the response and the cache store on a miss. Ships with a CacheHandler interface and a default in-memory LRU handler (register custom backends via registerCacheHandler), plus tag-based invalidation through unstable_revalidateTag(tag) that broadcasts across all Node Renderer workers via a new POST /cache/revalidate-tag endpoint and a Ruby-side ReactOnRailsPro::RSCCache.revalidate_tag(tag). Closes #3324. #3325 by AbanoubGhadban.

  • [Pro] Node Renderer integration API now exposes lifecycle hooks: react-on-rails-pro-node-renderer/integrations/api now exports the tracing reset, provider-state, Fastify lifecycle, and worker shutdown hooks needed by integrations such as OpenTelemetry, keeping integrations inside the supported public boundary. Fixes #3419. #3456 by justin808.

  • [Pro] Built-in HTTP rolling-deploy adapter (scaffold): New ReactOnRailsPro::RollingDeployAdapters::Http adapter pairs with a mountable ReactOnRailsPro::RollingDeploy::BundlesController so the currently-deployed Rails server can directly serve previously-deployed bundles to the next deploy's build CI — no S3 bucket, IAM, or extra gem required. The controller exposes authenticated GET /manifest and GET /bundles/:hash endpoints using bearer-token auth (constant-time compare, 32-byte minimum), and the adapter pulls bundle tarballs (stdlib-only gzip/tar compose-extract with path-traversal proofing, regular-files-only guards, and a 200 MB zip-bomb cap). Configure via config.rolling_deploy_adapter = ReactOnRailsPro::RollingDeployAdapters::Http, config.rolling_deploy_token, and config.rolling_deploy_previous_url. See docs/pro/rolling-deploy-adapters.md for setup. This is part 1 of a multi-PR series — a hard HTTPS gate, streaming download, and additional hardening land in follow-ups. #3379 by justin808.

  • [Pro] OpenTelemetry integration for the Node Renderer: New optional integration at react-on-rails-pro-node-renderer/integrations/opentelemetry that adds distributed tracing via standard OpenTelemetry. Users enable it by installing the @opentelemetry/* and @fastify/otel packages (optional peer deps) and calling init({ fastify: true, tracing: true }) from their renderer entrypoint, before reactOnRailsProNodeRenderer(). Provides auto-instrumented HTTP and Fastify spans, an SSR root span (ror.ssr.request), and render-path sub-spans (ror.bundle.build_execution_context, ror.bundle.upload, ror.vm.execute, ror.result.prepare, ror.incremental.stream, ror.incremental.process_chunk). Configuration follows standard OpenTelemetry env-var conventions (OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES, etc.); defaults to BatchSpanProcessor in production and SimpleSpanProcessor otherwise. The integration is fully optional — users who do not enable it pay zero runtime cost, and the renderer has no direct dependency on OpenTelemetry. Closes #2156. #3382 by justin808.

  • [Pro] Richer Node Renderer span attributes: ror.bundle.upload now records bytes.total (sum of bundle + asset upload source sizes); ror.vm.execute records bundle.timestamp; ror.result.prepare records response.bytes (UTF-8 byte length of the rendered response, omitted for streamed responses). Only byte counts and identifiers are recorded — request payloads and rendered HTML are never written into span attributes. The subSpan API now passes a SubSpanController to the wrapped function so integrations can attach attributes computed during the work; existing implementations must call fn(controller) (a no-op controller is fine when no span is created). Closes #3390. #3422 by justin808.

  • react-on-rails/webpackHelpers subpath export with reactDomClientWarning: New webpack helper export so React 16/17 consumers can suppress the harmless Module not found: Can't resolve 'react-dom/client' warning with a one-liner instead of remembering a regex. The require inside reactApis is guarded by a runtime React-version check, so this warning never reflects a real failure, but webpack still emits it at build time because the static require('react-dom/client') cannot be tree-shaken without breaking React 18+. Pass reactDomClientWarning to ignoreWarnings (Webpack 5 / Shakapacker) or stats.warningsFilter (Webpack 4 / Webpacker 5). Fixes #3137. #3358 by justin808.

  • bin/dev deterministic port allocation via REACT_ON_RAILS_BASE_PORT (and CONDUCTOR_PORT): bin/dev now derives Rails / webpack-dev-server / node-renderer ports from a single base port when REACT_ON_RAILS_BASE_PORT (or CONDUCTOR_PORT, for Conductor.build workspaces) is set: Rails = base + 0, webpack = base + 1, renderer = base + 2. This makes parallel worktrees and coding-agent sandboxes collision-free without per-service env vars. The priority chain is base port → explicit per-service env vars (PORT, SHAKAPACKER_DEV_SERVER_PORT) → auto-detection. Behavior note: when base-port mode is active, any pre-set PORT, SHAKAPACKER_DEV_SERVER_PORT, RENDERER_PORT, or non-matching REACT_RENDERER_URL (and the legacy RENDERER_URL, when already set) is unconditionally overwritten with the derived value (a warning is printed before each override). This applies in all bin/dev modes including bin/dev prod, where SHAKAPACKER_DEV_SERVER_PORT is also derived/overwritten for tooling consistency even though the production-like mode does not run webpack-dev-server. Sub-process env preservation: to keep these derived values consistent across spawned processes, bin/dev now also preserves RENDERER_PORT, REACT_RENDERER_URL, and SHAKAPACKER_SKIP_PRECOMPILE_HOOK across Bundler's env reset (previously only PORT and SHAKAPACKER_DEV_SERVER_PORT were preserved); this prevents nested shakapacker commands from silently re-running the precompile hook or losing the renderer URL. #3142 by justin808.

  • [Pro] bin/dev auto-derives REACT_RENDERER_URL from RENDERER_PORT: When only RENDERER_PORT is set, bin/dev now sets REACT_RENDERER_URL=http://localhost:RENDERER_PORT so Rails reaches the right port by default. Users running a remote or non-localhost node renderer (Docker service, remote host) should set REACT_RENDERER_URL explicitly so it is not replaced with the localhost default. #3142 by justin808.

  • [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. #3124 by justin808.

  • [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). #3173 by 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. #2903 by AbanoubGhadban.

Changed

  • [Pro] Rolling deploys can seed bundles from multiple endpoints: The built-in HTTP adapter's
    config.rolling_deploy_previous_urls setting accepts a URL string, comma-separated string, or Array,
    unions bundle hashes advertised by every endpoint, and fetches from each endpoint in order. This lets
    promoted images seed from both the build environment and the promotion target instead of missing the
    target's draining bundle. The matching build argument is ROLLING_DEPLOY_PREVIOUS_URLS. See
    Promotion deploys need a boot seed.
    #4544 by
    justin808.

  • [Pro] Published artifacts now identify the commercial license accurately: The Pro gem reports
    LicenseRef-LICENSE, while the react-on-rails-pro and react-on-rails-pro-node-renderer npm packages
    report SEE LICENSE IN LICENSE.md. All three packed artifacts include React on Rails Pro EULA v2.3 instead
    of presenting the software as UNLICENSED or shipping stale terms. EULA v2.3 clarifies organization-owned
    internal and public use, preserves free educational/demo use, and defines one product-level attribution per
    covered HTML document. All plan types and permitted free uses now require that attribution; a valid public
    attribution reports Licensed without exposing the Organization, while private server diagnostics retain it.
    #4660 by
    justin808.

  • [Pro] Reduced tag-index cache work for streamed cache writes: Multi-tag cache registration now
    batch-reads existing tag indexes when supported, and streamed cache misses defer cache writes and tag
    registration until queued response chunks have drained. Streamed and async cache misses also register tags
    with the same completion-time cache options used for the cache write, keeping TTL snapshots aligned. Fixes
    #4319.
    #4443 by
    justin808.

  • Lighter npx create-react-on-rails-app install: The scaffolder now colorizes its terminal output with the zero-dependency picocolors instead of chalk@4, dropping chalk and its transitive tree (ansi-styles, supports-color, color-convert, color-name, has-flag) from the package's runtime dependencies. This trims the weight of every cold npx create-react-on-rails-app install. The TTY color-fallback now also mirrors chalk@4/supports-color's actual signal precedence (recognized CI vendors, TERM, COLORTERM, Windows) instead of coloring any non-dumb TTY, matching the prior colored-output behavior in full. Fixes #4411 and #4455. #4444 and #4473 by justin808.

  • [Pro] Removed unused addressable/rainbow runtime gem dependencies: react_on_rails_pro.gemspec no longer declares direct runtime dependencies on addressable or rainbow — no Pro Ruby code referenced either gem, and both still arrive transitively through the required react_on_rails gem, so behavior is unchanged. Fixes #4416. #4422 by justin808.

  • [Pro] Fail fast for RSC with Rspack v1: When React Server Components are enabled and Shakapacker is
    configured for Rspack, app boot and react_on_rails:doctor now reject @rspack/core v1 or a missing
    @rspack/core package with explicit Rspack v2 upgrade instructions. This guard only runs when RSC is enabled,
    so Rspack v1 remains allowed for non-RSC apps, and bundler detection now honors SHAKAPACKER_ASSETS_BUNDLER
    before config/shakapacker.yml.
    #4289 by justin808.

  • Redux is now hidden from the V17 install generator path: The react_on_rails:install --redux option is no longer shown in install generator help or usage text, and recovery guidance no longer recommends --redux for new installs. The hidden legacy path and direct react_on_rails:react_with_redux generator now warn that Redux scaffolding is legacy while keeping runtime Redux APIs available. Closes #4272 and #4273. #4277 by justin808.

  • create-react-on-rails-app now defaults to Pro for React 19.2 support: Running
    npx create-react-on-rails-app my-app no longer asks setup questions and generates the recommended
    React on Rails Pro scaffold by default. Automation note: non-TTY environments, including CI and piped
    commands, previously auto-selected Standard mode; they now select Pro. Add --standard to any command
    that intentionally checks or creates an open-source-only scaffold. Use --rsc when you want Pro with the
    generated React Server Components example. The canonical reactonrails.com "Start a new app" AI-agent
    prompt (prompts.yml) was updated to match, so AI assistants following it also default to the Pro path
    and mention the license requirement. #4217 and
    #4232 by justin808.

  • [Pro] Missing renderer password error now leads with the local-development fix: When the Pro renderer password is unset and both RAILS_ENV and NODE_ENV are unset, the fail-closed error from both the Ruby configuration guard and the Node renderer now includes explicit export RAILS_ENV=development NODE_ENV=development guidance. Password-optional behavior for explicit development/test envs and password-required behavior for production-like or mixed envs are unchanged. Fixes #4201. #4211 by justin808.

  • Breaking (types only): RenderFunction no longer accepts the legacy 3-argument renderer shape: The exported RenderFunction type is now exactly the 2-argument server/client render-function form ((props, railsContext) => RenderFunctionResult), equal to the existing ServerRenderFunction. It previously also accepted a 3-argument (props, railsContext, domNodeId) => RenderFunctionResult arm, 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 typed RendererFunction. ReactComponentOrRenderFunction already includes RendererFunction, so renderer-shaped functions remain registerable. The tighter type also drops the re-narrowing as casts the unified type forced in createReactOutput and 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 as RenderFunction need to switch it to RendererFunction. Closes #3592. #4096 by justin808.

  • [Pro] RSC peer-compatibility warn-tier floor raised to stable 19.0.5: The Pro node renderer's recommendedMin for react-on-rails-rsc is now the published stable 19.0.5 (previously the dormant 19.0.2). Anyone still on an older 19.x build (19.0.219.0.4) now gets a loud startup warning that they are missing the coordinated RSC fixes shipped in 19.0.5 (FOUC stylesheet preloading, async manifest signatures); 19.0.5+ no longer warns. Refs #3632. #4078 by justin808.

  • [Pro] RSC peer wildcard replaced with an explicit range: The Pro npm package's optional react-on-rails-rsc peer dependency is now ^19.0.5 (>= 19.0.5 < 20.0.0) instead of "*", so installs are floored at the RSC CSS FOUC fix release 19.0.5 and capped below the next major 20.0.0, rather than "*" accepting any version including pre-FOUC builds and an unknown future major. Within the 19.x line, per-version compatibility is enforced by the Pro node renderer's runtime version check (rscPeerSupport.ts), not by this advisory peer range. Fixes #3965. #4082 by justin808.

  • [Pro] RSC peer compatibility accepts the coordinated React 19.2 floor: The Pro node renderer now allows React and React DOM 19.2.x starting at 19.2.7, matching the floor required by the react-on-rails-rsc 19.2 package line while preserving the existing React 19.0.x support window. Refs #3865. #4026 by justin808.

  • [Pro] <RSCRoute> client-control refetch failures are recoverable in production: ref and useCurrentRSCRoute() refetch failures now keep the last successful route content mounted in production, expose refetchError, retry(), and clearRefetchError() on RSCRouteHandle, and call the optional onRefetchError prop for parent/sibling reporting. Development still fails loudly through ServerComponentFetchError so component context and the original refetch error stay visible. Fixes #3565. #3786 by justin808.

  • Generator scaffolds the native RSCRspackPlugin for Rspack RSC projects: rails generate react_on_rails:install --rsc on an Rspack app now wires up react-on-rails-rsc/RspackPlugin (RSCRspackPlugin) instead of the Webpack-compat RSCWebpackPlugin, which produced valid-looking manifests that still broke under Rspack. Re-running the generator on a legacy Rspack config migrates the old RSCWebpackPlugin import and invocation to the native plugin. Resolves #3488. #3590 by justin808.

  • [Pro] Widened the react-on-rails-rsc peer-dependency range to the full React 19 line: react-on-rails-pro now declares react-on-rails-rsc as >= 19.0.2 < 20.0.0 (previously >= 19.0.2 <= 19.2.3), so future react-on-rails-rsc patch and minor releases on the React 19 line no longer trigger a peer-dependency warning. The react / react-dom peers stay at >= 16: React 18 support is retained, and the React-19-only RSC path is gated through the optional react-on-rails-rsc peer rather than by raising the React baseline. Resolves #3486. #3580 by justin808.

  • Generator defaults to Rspack for fresh installs: rails generate react_on_rails:install and create-react-on-rails-app now default to the Rspack bundler on fresh installs (significantly faster builds via SWC), instead of Webpack. Pass --no-rspack (or its alias --webpack) to use Webpack. This only affects fresh installs — existing apps that already declare an assets_bundler in config/shakapacker.yml are left unchanged, an explicit --rspack/--no-rspack/--webpack always wins, and the default falls back to Webpack on Shakapacker versions below 9.0 (where Rspack is unsupported). #3484 by justin808.

  • [Pro] RollingDeployCacheStager now rejects bundle hashes that start with a hyphen: The shared ReactOnRailsPro::RollingDeploy::SAFE_HASH_PATTERN constant (also used by the new HTTP rolling-deploy adapter) tightens the cache stager's old local pattern by additionally rejecting leading hyphens. Webpack content hashes never start with - in practice, so this is a no-op for default toolchains, but operators running a custom rolling-deploy adapter that emits hyphen-prefixed hashes will now see those hashes silently dropped from the staged set. If you depend on hyphen-prefixed hashes, rename them to start with an alphanumeric character or _. #3379 by justin808.

  • Upgrade contributor pnpm tooling to 10.33.4: The monorepo now pins pnpm 10.33.4 with Corepack's hash-qualified packageManager format, keeps the install-generator CI fallback on the same pnpm version, and relies on the root workspace pin instead of duplicate workspace packageManager declarations. #3400 by alexeyr-ci2.

  • Allow trusted pnpm 10 build scripts in contributor installs: The root workspace now allowlists required native dependency postinstall checks for @swc/core and unrs-resolver, so pnpm install under pnpm 10 no longer skips those trusted build hooks. #3421 by justin808.

  • Release publishing now checks origin/main CI status before shipping: rake release now inspects GitHub Checks for origin/main before publishing, blocking stable releases on any visible failing or missing checks and prereleases on required checks, with an explicit override path for maintainers. #3407 by justin808.

  • [Pro] Updated Pino in the Node Renderer: Raised the react-on-rails-pro-node-renderer pino dependency range to ^9.14.0 || ^10.1.0, aligning with the current Fastify dependency. #3401 by alexeyr-ci2.

  • [Pro] Async Rails server deployments use scheduler-scoped renderer clients: Falcon and async-rails deployments can use the async-http renderer client when a long-lived Fiber.scheduler is already running; renderer clients are reused within that scheduler. Standard Puma streaming uses a per-request scheduler and cleans up the client when the response ends. See docs/pro/updating.md for the full upgrade guide. #3320 by AbanoubGhadban.

  • [Pro] Per-scheduler persistent HTTP connections for Node Renderer: RendererHttpClient now reuses HTTP/2 connections across requests within the same Fiber scheduler (Falcon, async Puma), eliminating per-request TCP+TLS+HTTP/2 handshake overhead. Standalone requests (no outer scheduler) continue using ephemeral connections with guaranteed cleanup. The internal connection pool automatically recovers from broken connections without manual eviction. #3428 by AbanoubGhadban.

  • [Pro] Migrated Node Renderer HTTP transport from HTTPX to async-http: React on Rails Pro now uses async-http (~> 0.95) with io-endpoint (~> 0.17) for all Rails→Node Renderer requests (render, streaming render, asset upload), replacing the previous HTTPX adapter and the custom httpx_stream_bidi_patch.rb. The new RendererHttpClient is a request-scoped client (one client per Rails request — no persistent process-wide pool) and integrates with the length-prefixed wire protocol introduced in #2903. HTTP/2 bidirectional streaming for async props is now provided by post_bidi on the new adapter. Action required for upgraders:

    • config.ssr_timeout is now a per-read socket timeout applied to each renderer socket read, rather than a task-level timeout wrapping the entire request.
    • config.renderer_http_pool_timeout is now the TCP connect timeout; post-connect reads are bounded by ssr_timeout.
    • No implicit transport retry for connection drops: drops surface immediately as ReactOnRailsPro::Error/connection failures. HTTPX previously performed one implicit transport retry; the new adapter uses retries: 0 and leaves retry policy to the existing bundle-upload retry loop.

    See docs/pro/updating.md for the full upgrade guide. #3320 by AbanoubGhadban.

  • [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. #3173 by 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. #3124 by 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 #3073. #3165 by 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. #3142 by justin808.

  • Length-prefixed streaming wire protocol: The internal protocol between the Rails gems and the Node renderer (and the in-process bundle for the OSS non-streaming path) now uses a length-prefixed framing — <metadata JSON>\t<content byte length hex>\n<raw content bytes> — instead of wrapping every HTML chunk in a JSON envelope, eliminating ~30% serialize/escape overhead on streamed HTML and correctly handling multibyte content and chunk boundaries. This is an internal transport detail: React on Rails always ships the react_on_rails/react_on_rails_pro gems, the react-on-rails/react-on-rails-pro npm packages, and the react-on-rails-pro-node-renderer as a matched version set, and the Ruby parser also auto-detects the legacy JSON format, so no application action is required when upgrading all artifacts together. #2903 by AbanoubGhadban.

  • react_on_rails:doctor renderer-cache scan covers CI/CD manifests: The deprecated-task scan that flags react_on_rails_pro:pre_stage_bundle_for_node_renderer now also checks .circleci/config.yml, .gitlab-ci.yml, bitbucket-pipelines.yml, every .github/workflows/*.yml/.yaml, and every config/deploy/*.rb stage file, on top of the existing Procfile/Dockerfile/Compose/Kamal/Capistrano/bin/*/scripts/deploy.sh paths. The scan stays bounded: per-file size cap, no ** globs, a per-glob match cap, per-file rescue, and a separate per-glob rescue so a single unreadable workflow or stage file cannot abort the rest of the scan. Fixes #3247. #3329 by justin808.

  • Rspack install scaffolding now targets Rspack v2: react_on_rails:install --rspack and bin/switch-bundler now generate the Rspack v2 package line (@rspack/core@^2.0.0-0, @rspack/cli@^2.0.0-0, @rspack/plugin-react-refresh@^2.0.0) while keeping rspack-manifest-plugin@^5.0.0, which is already compatible. Closes #3082. #3084 by justin808.

Improved

  • [Pro] Embedded RSC payloads no longer repeat full serialized props in the page HTML: The embedded RSC payload cache key now uses a compact hash of the component props instead of the full JSON.stringify(componentProps), so server-rendered pages with RSC components stop repeating large props JSON once per Flight chunk in render-blocking inline scripts (a 1KB props object across 10 chunks previously added ~12KB of repeated JSON; now ~120 bytes). The client-side RSCProvider in-memory promise cache still keys on full props, so payload deduplication behavior is unchanged. Fixes #3796. #3800 by AbanoubGhadban.
  • [Pro] RSC manifest setup avoids synchronous file-stat work on render: Pro RSC rendering no longer relies on synchronous fs.statSync manifest signature checks during render-time setup, avoiding Node event-loop blocking on that path. The manifest-wide CSS/link cache path that used those signatures was later removed by #3860. #3595 by justin808.
  • Server-bundle load failures are now classified separately from renderer-connection failures: Bundle read/fetch failures raise the new ReactOnRails::ServerBundleLoadError instead of being grouped with renderer-connectivity errors, keeping the renderer-connection classification (see #3614) focused on render-origin connection failures, including wrapped connect(2) errors found through the exception cause chain. Fixes #3628. #3724 by justin808.
  • Renderer connection failures are no longer misreported as webpack/bundle errors: When server rendering fails because Rails cannot reach the renderer process (for example a Pro Node renderer behind REACT_RENDERER_URL that refuses the connection — ECONNREFUSED, EHOSTUNREACH, ETIMEDOUT, or a sandboxed Errno::EPERM / connect(2) failure), React on Rails now raises a renderer-connectivity error that names the host/port it tried to reach and points at REACT_RENDERER_URL and renderer liveness, instead of the misleading "Error evaluating server bundle. Check your webpack configuration." The existing webpack/server-bundle troubleshooting is retained for genuine bundle evaluation errors. Fixes #3604. #3614 by justin808.
  • RSC setup verification warns on dynamic plugin options: react_on_rails:install --rsc now warns when new RSCWebpackPlugin(...) uses computed options that cannot be statically verified, avoiding misleading missing-clientReferences reports for dynamic config. Fixes #3412. #3505 by justin808.
  • Rspack-aware diagnostics and dev-server help: Doctor, system checker, and bin/dev --help output now label Rspack apps as Rspack instead of webpack while preserving webpack wording for default apps. Fixes #3388. #3508 by justin808.
  • Resolved Shakapacker config path warnings now show the expanded path: Missing SHAKAPACKER_CONFIG warnings now include the Rails-root-resolved path that was checked, making relative-path typos easier to diagnose. #3444 by justin808.

Fixed

  • [Pro] Failing RSC payloads no longer cause unbounded browser requests: RSCProvider now
    keeps one cached Promise for a logical payload load and retries a transient network, server, or
    malformed-payload failure once within it. If the retry also fails, React receives the final
    rejection instead of repeatedly starting new requests. The retry bypasses failed embedded
    payloads and fetches fresh data; 4xx responses and cancellations are not automatically retried,
    and official server rendering does not perform the browser retry. A later browser lookup may try
    again after a short retention window. Fixes
    react_on_rails_rsc Issue 187.
    #4564 by
    ihabadham.

  • [Pro] Production streamed RSC CSS reveal gating: Pro streaming now promotes stylesheet
    preloads listed in the React client manifest, preventing a flash of unstyled content when
    production chunk CSS uses numeric IDs and id-named files. Fixes
    #4568.
    #4570 by
    justin808.

  • [Pro] RSC payload prerender cache no longer stores an empty payload: With
    config.prerender_caching = true, the RSC payload endpoint (/rsc_payload/:component_name) served
    the first visitor a correct payload but every subsequent visitor a zero-byte payload, because the
    length-prefixed framing consumer destructively removed "html" from the chunk Hash that
    StreamCache had buffered by reference and wrote to the cache after the stream completed. The
    browser's Flight client then rejected the empty response with "Connection closed.". The framing
    consumer now reads "html" without mutating the chunk, and StreamCache snapshots each chunk
    before handing it downstream so a mutating consumer can never corrupt the cached entry. Fixes
    #4550.
    #4551 by
    AbanoubGhadban.

  • [Pro] Deferred RSC route failures now reach app error boundaries: Rejected deferred RSC route
    fetches stay observable long enough for React error boundaries to render their fallback, while failed
    entries release their in-flight cache pin without evicting unrelated healthy payloads under concurrent
    load. Fixes #4522.
    #4529 by
    justin808.

  • [Pro] RSC doctor and version checks resolve Rspack from the configured app dependencies:
    RSC artifact diagnostics and startup version checks now resolve @rspack/core from the configured
    client node_modules path, avoiding false Rspack verification failures when the app's package root
    differs from the current process context. Fixes
    #4523.
    #4530 by
    justin808.

  • hydrate_on: visible no longer leaks a detached root or blocks re-hydrating a replacement node: When a hydrate_on: visible target was detached from the DOM before it became visible, the intersection observer left a stale scheduled root entry that kept the removed node reachable and could stop a replacement node with the same DOM id from scheduling its own hydration. The observer now runs its scheduled callback after disconnecting from a detached target; the callback's existing isConnected guard deletes the stale entry without hydrating the removed node, so a fresh node with the same id schedules cleanly. Fixes #4328. #4374 by justin808.

  • [Pro] RSCRoute error recovery now covers synchronous route failures and fire-and-forget retries: Synchronous throws from RSC route fetch/render setup are normalized into rejected fetch promises so they flow through the same RSCRoute error-recovery path (and RSCRouteErrorBoundary) as async fetch failures instead of bypassing it, keeping retry/refetch bookkeeping consistent when a fetch fails synchronously. Fire-and-forget recover-on-error retries also no longer surface as unhandled promise rejections in production: the caller-visible retry promise stays rejected for callers that await or catch it, while an internal no-op handler prevents an unhandled rejection when the retry is not awaited. Fixes #4330 and #4372. #4378 and #4393 by justin808.

  • [Pro] Node renderer rejects unsafe asset filenames: The Pro Node renderer now rejects path-like,
    drive-relative, alternate-stream, and control-character asset filenames before uploaded assets are copied
    or /asset-exists probes bundle directories, returning a 400 without reporting rejected names to the
    error reporter. #4285 by
    ihabadham.

  • [Pro] Streamed RSC responses now append the Node renderer's ror_renderer_prepare
    metric to the browser-visible Server-Timing header while preserving existing
    Rails/application entries, so rsc_stream_observability exposes renderer prepare
    time without relying on HTTP trailers. Closes
    #4479.
    #4487 by
    justin808.

  • [Pro] Streaming component caches honor cache_options on reads: cached_stream_react_component
    wrote cached chunks with the user-supplied cache_options but read them back without any options, so
    key-altering options such as namespace: meant the cache never hit and every request re-streamed from
    the renderer (a bare read could even return a stale un-namespaced entry for the same logical key). Reads
    now pass the same normalized options as the deferred write, and the lower-level streaming prerender cache
    (ReactOnRailsPro::StreamCache.fetch_stream) likewise accepts and applies the cache_options its write
    path uses. #4452 by
    ihabadham.

  • [Pro] Sync RSC route failures surface as ServerComponentFetchError: Synchronous throws
    in the RSC payload path (payload key creation on BigInt/circular props, sync
    getServerComponent throws, fetchRSC request preparation) previously bypassed
    RSCRouteErrorBoundary, so user error boundaries received raw TypeErrors without component
    metadata. All sync and async failure paths now funnel through the boundary, and a nested route's
    already-wrapped error passes through without re-wrapping. Fixes
    #4372.
    #4438 by
    justin808.

  • [Pro] RSC payload streaming flushes complete HTML before incomplete tails: The Pro RSC
    payload injector now streams the complete prefix of each chunk immediately while retaining only
    incomplete UTF-8 sequences, HTML tags, comments, template content, raw-text elements, or
    foreign-content tails for the next chunk. This preserves progressive streaming when a chunk ends
    in partial markup instead of holding the entire flush until the tail completes. Fixes
    #4327.
    #4379 by
    justin808.

  • [Pro] Truncated RSC parser streams now warn at EOF: The Pro RSC length-prefixed stream
    parser flushes at stream end so incomplete trailing records emit the existing parser warning,
    while expected request-abort cleanup remains quiet.
    #4392 by
    justin808.

  • ReactOnRails.getStore(name, false) no longer throws when no stores are hydrated: With
    throwIfMissing = false, getStore now returns undefined when the hydrated-store registry is
    empty — matching its documented contract and its existing behavior when other stores are hydrated —
    in both the open-source and Pro JS packages. Default strict calls still throw the descriptive
    "There are no stores hydrated" / "Could not find hydrated store" errors.
    #4457 by
    ihabadham.

  • [Pro] RSC stylesheet inference retries after transient stats read failures: Pro now caches
    client-chunk stylesheet metadata only after a successful loadable-stats.json read, so a
    deploy-race or temporary parse/read failure does not disable inferred RSC client stylesheets for
    the worker lifetime.
    #4401 by
    justin808.

  • [Pro] RSC loadable-stats retry diagnostics stay visible: Malformed or unreadable
    loadable-stats.json warnings are suppressed only for the retry window, and native ESM stack
    paths rewritten through source maps are normalized back to the runtime lib/ directory before
    resolving stats.
    #4447 by
    justin808.

  • [Pro] RSC client fetch failures no longer include serialized props in thrown error messages or causes:
    Browser RSC requests still send the real ?props=... payload to the Rails endpoint, but pre-response fetch
    failures now report the query-free component source path with a stable generic message so console logs, error
    boundaries, and error reporters do not capture accidental sensitive values from props. Retry props remain
    directly readable for refetches but are no longer enumerable error fields. Fixes
    #4449.
    #4450 by
    ihabadham.

  • [Pro] Node renderer graceful shutdown and scheduled restarts: Worker shutdown now counts
    each active request once across response, abort, and timeout hooks; scheduled restart timeouts use
    documented seconds; draining workers skip only the early master SIGKILL while keeping the hard
    shutdown deadline; and stale scheduled-restart workers no longer break the restart loop. Fixes
    #4365,
    #4366,
    #4367, and
    #4368.
    #4400 by
    justin808.

  • [Pro] Response-start send failures are reported during abandoned incremental renders:
    The Pro node renderer now observes and reports rejected response-start/send promises when
    incremental render request handling stops early or errors after a response starts, while still
    propagating those failures on clean stream completion. Fixes
    #4364.
    #4389 by
    justin808.

  • [Pro] Async-props prerender stream cache isolation: Pro prerender stream caching now
    bypasses renders that use async props, so per-request async stream output cannot be replayed from
    another request's cached stream. Fixes
    #4359.
    #4376 by
    justin808.

  • Preload links stay compatible with older Shakapacker: react_on_rails_preload_links now
    skips SRI attributes when Shakapacker does not expose integrity settings, avoiding a
    NoMethodError while still emitting preload hints. Fixes
    #4369.
    #4377 by
    justin808.

  • [Pro] Renderer HTTP transport no longer buffers successful streams or duplicate bundle uploads:
    Pro renderer requests now pass successful streaming response chunks through without retaining them in Ruby
    memory, reuse a persistent async-http client for plain Puma non-streaming requests, reset scheduler-scoped
    clients when the shared renderer connection is reset, and stream/deduplicate same-bundle uploads. Plain Puma
    reuse is per request-handling thread, so total renderer connection capacity can scale with Puma threads times
    renderer_http_pool_size; tune the renderer and any intermediaries accordingly. Fixes
    #4360,
    #4361,
    #4362, and
    #4363.
    #4394 by
    justin808.

  • [Pro] Streaming dependency load errors stay visible: Pro streaming cleanup now tolerates dependency
    load failures that happen before stream observability state is captured, so the original LoadError
    remains visible instead of being masked by cleanup. Fixes
    #4324.
    #4388 by
    justin808.

  • [Pro] Tag revalidation keeps retry metadata after entry delete failures:
    ReactOnRailsPro.revalidate_tag now restores the tag index before re-raising when tagged
    cache-entry deletion fails, so transient cache-store failures leave retry metadata instead
    of orphaning stale entries until their TTL expires. Fixes
    #4317.
    #4375 by
    justin808.

  • hydrate_on: nil falls back to immediate hydration: Passing hydrate_on: nil now behaves
    like the default :immediate mode instead of raising, while invalid explicit values still fail
    fast. Fixes #4342.
    #4350 by
    justin808.

  • [Pro] Incremental render setup failures release renderer context:
    The Pro node renderer now releases the execution context and destroys started streams when
    pull-mode incremental render setup fails, preventing orphaned renderer work after response-start
    errors. Fixes #4312.
    #4383 by
    justin808.

  • [Pro] Dropped pull-mode prop requests are logged: Streaming SSR now warns when the Node
    renderer sends a propRequest control frame without an emitter or with a missing, empty, or
    oversized propName, making dropped pull-mode requests diagnosable while preserving valid
    enqueue and renderComplete behavior. Fixes
    #4314.
    #4352 by
    justin808.

  • [Pro] RSC preload replay survives cache eviction: Preloaded RSC payloads now remain
    replayable after route cache eviction, preventing stale preload state from breaking refetches.
    Fixes #4326.
    #4353 by
    justin808.

  • [Pro] Incremental stream timers stay aligned with active chunks: Healthy pull-mode
    incremental streams are no longer closed by stale request or finish timers while chunks keep
    progressing, while abandoned streams remain bounded by an idle watchdog. Fixes
    #4310 and
    #4311.
    #4354 by
    justin808.

  • [Pro] RSC Rspack boot validation warns on undetermined versions: Boot now warns and continues when
    the active Rspack version cannot be determined, while strict doctor checks and provable Rspack v1
    failures still fail closed. Fixes #4340.
    #4355 by
    justin808.

  • [Pro] Registry cleanup handles page-unload cancellations safely: Pending component and store
    registry waits are now rejected when a page unloads or store generators are cleared, stale registry
    timeouts are cleared, and expected navigation cancellations are ignored during client rendering while
    real registration failures still surface. #4282
    by justin808.

  • [Pro] Gemfile loader source encodings are honored under C/POSIX locales:
    The Pro Gemfile now loads its shared dependency fragments in binary mode, applies Ruby
    source-encoding magic comments or a UTF-8 default, and validates content before override-gem
    scanning and evaluation. This affects any environment that evaluates the Pro Gemfile through
    Bundler, including local setup and CI, so Pro apps with non-ASCII dependency comments no longer
    fail under shells where Ruby's default external encoding is US-ASCII. Fixes
    #4276.
    #4281 by
    justin808.

  • [Pro] Cached component hits load generated packs consistently:
    Pro cached component helpers now run the cache-hit pack-loading path consistently, so cached
    cached_react_component and cached_react_component_hash output preserves generated pack behavior. Fixes
    #4316.
    #4384 by
    justin808.

  • Precompile hook no longer forces UTF-8 onto a non-UTF-8 locale:
    The shared Shakapacker precompile hook now widens a spawned bundle exec / shakapacker subprocess
    to UTF-8 only under a bare C/POSIX locale, where the locale-derived encoding is US-ASCII — a
    strict subset of UTF-8, so the widening cannot corrupt genuinely-ASCII content. Under a real
    national locale (for example a Brazilian developer's LANG=pt_BR.ISO8859-1) it now leaves
    LANG/LC_ALL/RUBYOPT untouched and lets the child inherit the working locale, instead of
    force-pinning -EUTF-8 and re-decoding the developer's latin-1/CP1252 source files as UTF-8 (which
    raised invalid byte sequence in UTF-8). The locale gate reads Encoding.find("locale") so it is
    not masked by Rails setting Encoding.default_external to UTF-8 at boot. This removes the
    RUBYOPT-rewriting machinery added in
    #4231 while keeping the original
    C/POSIX-locale crash fix from
    #4169.
    #4244 by
    justin808.

  • [Pro] RSC Rspack doctor no longer false-warns on equivalent lazyCompilation configs:
    react_on_rails:doctor:rsc only recognizes the generated literal
    clientWebpackConfig.lazyCompilation = false assignment. Apps that disable lazy
    compilation an equivalent way (object form, Object.assign, a helper, a ternary,
    or a different config file) now get a warning that says doctor could not confirm
    the setting rather than asserting lazy compilation is still enabled, plus guidance
    to confirm the effective dev-server config and ignore the warning if it is already
    disabled. Follow-up to
    #4234.
    #4249 by
    justin808.

  • Generated Tailwind apps load Tailwind from the layout. The install generator now declares Tailwind through a layout-owned pack instead of component-owned imports, keeps generated layout head metadata mobile/CSP-ready, and warns safely when custom layouts need manual pack-tag replacement. #4182 by ihabadham.

  • [Pro] Rspack RSC dev-server setup is easier to diagnose and customize:
    Generated RSC helper code now verifies client-reference discovery support
    through the sibling rscWebpackConfig.js file instead of assuming
    config/webpack, so Rspack apps keep using config/rspack/rscWebpackConfig.js.
    The RSC doctor also warns existing Rspack apps when normal bin/dev can leave
    the React Client Manifest empty because lazyCompilation is still enabled,
    and the troubleshooting guide documents the lazy-compilation-proxy /
    POST /_rspack/lazy/trigger 404 symptom path. Follow-up to
    #4227.
    #4234 by
    justin808.

  • Precompile hook UTF-8 hardening now handles conflicting RUBYOPT encoding flags:
    The shared Shakapacker precompile hook strips pre-existing Ruby encoding flags such as
    -EUS-ASCII, --encoding=US-ASCII, --external-encoding=US-ASCII, and
    --internal-encoding=US-ASCII before pinning subprocesses to -EUTF-8:UTF-8, so C/POSIX-locale
    builds do not crash when the parent shell exports a conflicting RUBYOPT. The shipped generated
    bin/shakapacker-precompile-hook template now uses the same UTF-8 subprocess environment for RSC
    client-reference discovery. Follow-up to
    #4169.
    #4231 by
    justin808.

  • [Pro] Generated RSC + Rspack apps render in normal bin/dev:
    RSC + Rspack generator output now disables Rspack lazy compilation while the
    dev server is running, so discovered client references are compiled before
    the React Client Manifest is read during server rendering. Fresh generated
    apps no longer fail /hello_server with an empty react-client-manifest.json
    in normal HMR mode. Fixes Issue 4226.
    #4227 by
    ihabadham.

  • [Pro] RSC doctor now catches stale install and client-manifest setup failures:
    The doctor now validates installed react-on-rails-rsc peer requirements
    against react and react-dom, warns when the installed RSC package is
    behind newer prerelease npm dist-tags, and reports missing, dev-server-backed,
    invalid, or empty RSC client manifests with bin/dev static and clean rebuild
    guidance. Pro RSC render errors that fail to resolve a React Client Manifest
    module now include the same stale/empty/cross-version manifest hint instead of
    leaving the upstream "probably a bug in the RSC bundler" text as the only clue.
    Fixes #4198
    and #4200;
    addresses #4199
    and scopes #4204.
    #4213 by
    justin808.

  • Dummy app setup uses native Nokogiri gems on macOS: The dummy app lockfile now includes Darwin platforms so bin/setup installs native Nokogiri gems on Apple Silicon and Intel macOS instead of attempting a source build that can fail with a missing nokogiri_gumbo.h header. #4218 by justin808.

  • [Pro] ScoutApm Node renderer instrumentation no longer depends on Gemfile load order: Pro now installs NodeRenderingPool.exec_server_render_js instrumentation from a Rails engine initializer that runs after scout_apm.start, instead of checking defined?(ScoutApm) at class load time. Apps without ScoutApm still boot normally, and apps that load scout_apm after react_on_rails_pro no longer silently skip the Pro Node renderer span. Fixes #4208. #4210 by justin808.

  • [Pro] RSCProvider now evicts a rejected getComponent promise so transient failures can retry: When the underlying RSC fetch for getComponent rejected — a transient renderer/network/deploy failure — the rejected promise stayed in the provider's bounded payload cache, so every later same-key getComponent returned that cached rejection and the RSC route/component stayed wedged in its error state even after the backend recovered; only an explicit refetchComponent or a full reload cleared it. getComponent now attaches a rejection handler that re-throws (so React's Suspense machinery still observes the failure) and evicts the cached entry one macrotask later, guarded on promise identity so a newer same-key getComponent/refetchComponent started in that window is never evicted by the stale failure. Pins are preserved so the existing .finally() still owns the matching unpin. Payloads that resolve with an Error value are intentionally left cached — that retryability is a separate getServerComponent contract decision. Fixes #3929. #4189 by justin808.

  • [Pro] Precompile hook no longer crashes under a non-UTF-8 (C/POSIX) locale: The shared Shakapacker precompile hook now forces a UTF-8 locale on every bundle exec / shakapacker subprocess it spawns — pack generation, the i18n locale generation added in #4128, and the RSC client-reference discovery build. Without LANG/LC_ALL set, those children inherited a US-ASCII default external encoding and died parsing Gemfiles containing non-ASCII bytes (e.g. react_on_rails_pro/Gemfile.loader: invalid byte sequence in US-ASCII), aborting the entire precompile. Extends the UTF-8 hardening from #3949 from the hook's own file reads to the subprocess boundary. #4169 by justin808.

  • OSS renders no longer compute or emit Pro-only generated-stylesheet metadata: Rendering a component
    now skips generated_stylesheet_hrefs_json's stylesheet-href discovery entirely when React on Rails Pro
    is not installed, instead of running that Pro-only work on every open-source render. Fixes
    #4341.
    #4395 by
    justin808.

  • create_render_options no longer mutates the caller's options hash: Adding default store
    dependencies and internal stream-observability state now operates on a duplicate of the supplied
    options, so a frozen or reused options hash passed to a helper no longer raises FrozenError or leaks
    observability state into a later render that reuses the same hash. Fixes
    #4343.
    #4396 by
    justin808.

  • Locale-file regeneration check no longer reads the whole generated file, and correctly detects a
    legacy import after custom content
    : The obsolete-locale-file check now streams default.js line by
    line, stopping as soon as it finds either the legacy react-intl/defineMessages import (regenerate)
    or the current const defaultLocale = ... marker (up to date), instead of reading and matching the
    entire file from the start. This also fixes a false negative where a legacy import following custom
    file content was not detected. Fixes #4345.
    #4398 by
    justin808.

  • [Pro] Static RSC payload script stripping is robust to generated body-shape changes: Pro-generated
    RSC initialization, payload-chunk, and diagnostic <script> tags now carry an explicit
    data-react-on-rails-rsc-payload="true" marker, and static RSC cache stripping
    (cached_static_rsc_component) now prefers that marker over inferring payload scripts from body shape,
    while still falling back to the previous shape-based detection for older generated HTML. Fixes
    #4459.
    #4477 by
    justin808.

  • Deferred hydration error reporting handles non-Error thrown values: Delayed hydrate_on renders now normalize strings, null, and frozen Error instances before logging, so reporting the failure does not throw again or mutate user errors. #4120 by ihabadham.

  • Explicit Webpack installs now pass the resolved bundler to Shakapacker. rails generate react_on_rails:install --no-rspack and --webpack now set SHAKAPACKER_ASSETS_BUNDLER=webpack before running shakapacker:install, so Shakapacker installs Webpack dependencies instead of falling back to its default bundler. Fixes #4108. #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_path settings, including slash entry roots, while wrapping long source hints in the generated demo views. Fixes #4062. #4107 and #4130 by justin808.

  • RSC-safe generated i18n locale defaults: The JavaScript locale compiler that generates default.js no longer imports react-intl or wraps messages in defineMessages; 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-oriented react-intl entrypoint, and without raising the minimum supported react-intl version. The exported defaultMessages shape is unchanged, and existing apps regenerate automatically because the compiler treats a default.js still using the old defineMessages template as stale. Fixes #4132. #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's PipeableStream.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's cacheSignal, which React settles automatically once a render is aborted (the cacheSignal-specific test and docs remain a follow-up). Part of #3885. #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 the versions/successfulVersions state maps) are now backed by a bounded LRU (default cap 50 distinct RSC payload keys). High-cardinality componentProps (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, recoverOnError restore, 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-key useSyncExternalStore subscription/fan-out optimization from the same issue is intentionally deferred pending profiling. Refs #3564. #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's onError as 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, so ReactOnRails::PrerenderError / SmartError output names the failing RSC component and module instead of a bare React message. Since React's onError carries 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 #3475. #4100 by justin808.

  • [Pro] RSC and client-only FOUC reveal gating: Pro now waits for auto-loaded generated component stylesheets before mounting client-only roots and promotes streamed RSC client chunk stylesheet preloads to real stylesheet links before reveal, including when stream chunks split <link> tags. Shared generated CSS is matched through manifest-derived stylesheet href metadata, while app-authored preload links remain untouched. Fixes #4031, #4032. #4047 by justin808.

  • Rspack generated apps start in HMR mode: Fresh rails generate react_on_rails:install --rspack and create-react-on-rails-app projects now install @rspack/dev-server, use the ReactRefreshRspackPlugin export, and keep bin/switch-bundler rspack's dev dependencies complete so bin/dev can launch Rspack serve instead of crashing during dev-server startup. Fixes #3925. #3926 by AbanoubGhadban and ihabadham.

  • [Pro] Node renderer drains workers on SIGTERM/SIGINT instead of orphaning them: The Pro Node renderer master now installs SIGTERM and SIGINT handlers before forking workers, so supervisors (Foreman, local dev, process managers) that signal the master gracefully drain workers — sending the shutdown message, waiting a short grace period, calling cluster.disconnect(), then awaiting each worker's exit event — instead of killing only the master and leaving orphaned worker processes behind. Shutdown uses signal-style exit codes (SIGINT → 130, SIGTERM → 143), reuses a shutdown-time worker snapshot for the hard SIGKILL fallback so disconnected-but-still-running workers are not missed, and suppresses reforks while a shutdown is in progress. Fixes #3863. #3882 by justin808.

  • [Pro] RSC unstable_cache no longer caches failed Flight renders: React Flight render errors reported by renderToPipeableStream now skip the cache write, so later requests retry the render instead of replaying a failed RSC payload from cache. React's default error logging behavior is preserved. Fixes #3774. #3775 by ihabadham.

  • [Pro] Prerender cache no longer misses when only the random domNodeId changes: Pro prerender cache digest normalization now strips domNodeId values emitted in JSON/double-quote form as well as the older single-quote form, so cached prerender results are reused across requests that differ only by the randomly generated DOM node id, while changed props still produce distinct digests. Fixes #3706. #3707 by ihabadham.

  • Auto-bundled snake_case component names now load their generated packs consistently: Auto-bundled component pack loading is normalized through the same name resolution used for DOM/SSR lookup, so react_component("component_name") loads the generated/ComponentName pack, and public component names generated for files under the configured auto-bundling components subdirectory are camelized (server-bundle and store filename behavior is preserved, as is generated-pack conflict protection for names differing only by case). Fixes #3809. #3818 by justin808.

  • [Pro] A throwing React root unmount no longer aborts client teardown: The Pro client renderer now guards the modern React root unmount() path, logging failures at console.error and always clearing the tracked root, so one failing root unmount cannot stop other components from unmounting or block later renders on the same DOM node. Fixes #3618. #3716 by justin808.

  • [Pro] Plain-object component registrations keep their type in the Pro ComponentRegistry: Pro registry entries are now typed to cover plain object modules used by server_render_js (matching the core package types from #3606), getOrWaitForComponent aligns with the widened entry type, and the Pro client renderer now guards against invoking non-callable renderer entries. Fixes #3677. #3719 by justin808.

  • Server-component wrapper types reject non-component render functions at compile time: The new ReactComponentRenderFunction type models render functions that must return a React component, and the Pro wrapServerComponentRenderer client/server inputs are narrowed to it, so registering a teardown-returning renderer function there is now a TypeScript compile-time error instead of relying only on the existing runtime guards (which are unchanged for JavaScript callers). Fixes #3589. #3720 by justin808.

  • [Pro] Incompatible react-on-rails-rsc / React versions now fail loudly instead of silently misbehaving: the Pro node renderer checks the installed react-on-rails-rsc, react, and react-dom versions at boot and throws a clear error when they are outside the supported React 19.2.x line. Set REACT_ON_RAILS_PRO_DISABLE_VERSION_CHECK=1 to downgrade the hard error to a warning. #3831 by justin808.

  • [Pro] RSC React.cache() request dedupe now works in generated configs: Generated RSC webpack configs now canonicalize react, react/jsx-runtime, and react/jsx-dev-runtime resolution to one React server package instance, keeping React's RSC cache dispatcher shared between the renderer and app Server Components. This restores request-local React.cache() memoization for direct Server Component data loaders in generated RoRP RSC apps. Fixes #3812. #3813 by ihabadham.

  • [Pro] Preloaded RSC hydration errors preserve bundle diagnostics: RSC payload injection now threads the server-side renderingError metadata to the browser so preloaded hydration failures can report the original RSC bundle error message and module path, matching the fetch path while avoiding unrelated stream metadata such as serialized props. Addresses the preloaded hydration portion of #3475. #3766 by justin808.

  • Auto-bundled component name conflicts now fail loudly: React on Rails now raises an error when multiple auto-bundled component files resolve to the same public component name, listing the conflicting files instead of silently keeping whichever path overwrote the earlier mapping. Fixes #3708. #3709 by ihabadham.

  • TypeScript source server bundles work with auto-generated packs: React on Rails now resolves the configured server bundle source entrypoint by extension, so apps can keep config.server_bundle_js_file = "server-bundle.js" as the compiled/runtime bundle name while using a TypeScript source entrypoint such as packs/server-bundle.ts. Public registration types also now cover plain object modules used by server_render_js, matching existing runtime behavior. Resolves #1583. #3606 by ihabadham.

  • [Pro] Client teardown failures are no longer hidden at console.info: when ComponentRenderer.unmount() catches an error from unmountComponentAtNode (the React 16/17 legacy unmount path), it now logs at console.error instead of console.info. A caught error there means the component tree did not unmount cleanly — a teardown failure — and most log collectors and default browser-console filters drop info, so the failure was effectively silent. Addresses item 2 of #3592. #3610 by justin808.

  • Renderer functions no longer leak their mount on navigation/unmount: Renderer functions (the 3-argument (props, railsContext, domNodeId) => … registration form) own their own React root, but React on Rails never tracked any cleanup state for them, so every renderer-function mount leaked on Turbo/Turbolinks navigation. Renderer functions may now optionally return a teardown wrapper ({ teardown: () => void | Promise<void> }, sync or async); returning nothing keeps the previous behavior, so existing renderers are unaffected. Both the core and Pro client renderers invoke the teardown on page unload and on same-id node replacement, and cleanup failures on same-id replacement are now logged to console.error instead of only being visible when tracing is enabled. The renderers differ only in the async race: if a navigation unmounts the mount while an async renderer is still resolving its teardown, Pro still runs the teardown once it resolves, whereas the core renderer is best-effort and may drop a still-pending async teardown while the renderer is awaiting dynamic imports, fetches, or other I/O on a fast navigation; active async renderer failures are logged and then untracked so a later load call can retry. The framework-shipped Pro wrapServerComponentRenderer now returns such a teardown wrapper, closing the leak automatically for every registerServerComponent user. TypeScript note: the exported RendererFunction type covers 3-argument renderers that return nothing or an optional teardown wrapper; RenderFunction keeps its existing component/server-result return contract, including legacy 3-argument renderers that returned a component only to satisfy the old type. Fixes #3209. #3576 by justin808.

  • Shakapacker config warnings now report resolved relative paths: When SHAKAPACKER_CONFIG is set to a relative missing path, the Rails boot warning now includes the Rails-root-resolved path that React on Rails actually checked. Fixes #3436. #3441 by justin808.

  • Base-port renderer URLs preserve localhost-equivalent hosts: bin/dev base-port mode now keeps localhost-equivalent renderer hosts and schemes, such as 127.0.0.1 and https://localhost, when deriving REACT_RENDERER_URL; remote or invalid hosts still fall back to http://localhost:<port>. Fixes #3466. #3506 by justin808.

  • [Pro] Streamed RSC rendering now propagates CSP nonces: React on Rails Pro now passes the Rails CSP nonce to React's streamed RSC renderer options so streamed script output can satisfy strict content security policies. Fixes #3491. #3507 by justin808.

  • [Pro] RSC stream parser tolerates blank separator lines: LengthPrefixedStreamParser now skips blank separator lines (including a lone CR from a split CRLF) between length-prefixed records instead of treating them as malformed headers, matching the default Pro RSC payload template that inserts extra newlines between chunks. Malformed non-empty headers still raise as before, and a stream ending mid-\r\n no longer logs a spurious incomplete-stream warning. Fixes #3499. #3515 by justin808.

  • Generated build scripts run the auto-bundle hook before building bundles: Newly generated apps now run the Shakapacker precompile (auto-bundle) hook before the generated build, build:test, scaffolded CI, and build_test_command bundle builds, so packs are regenerated before bundling. Apps with custom Shakapacker hooks keep them under Shakapacker's control so the hook does not double-run. #3535 by justin808.

  • [Pro] RSC client-hook runtime errors now explain the missing client boundary: React on Rails Pro now rewrites RSC runtime hook failures such as useState is not a function with a diagnostic that names the registered component, points to the likely missing "use client"; directive, and clarifies that .client/.server suffixes only control bundle placement. Fixes #3184. #3461 by justin808.

  • Test asset compiler output is now bundler-neutral: Test asset compilation now prints "Building assets..." and "Completed building assets." instead of Webpack-specific wording, and failure guidance tells users to rerun their configured build_test_command instead of naming Shakapacker. Fixes #3455. #3462 by justin808.

  • Test helper dev-asset guidance is now bundler-neutral: HMR warnings, missing generated-file guidance, and test-helper comments now refer to the configured bundler/dev server instead of Webpack or Shakapacker-specific commands while keeping the existing public WebpackAssets* API names. Fixes #3478. #3513 by justin808.

  • [Pro] RSC stream failures now surface original diagnostics: RSC payload stream metadata now preserves the original RSC bundle exception message, stack, component name, and module path where available across server-bundle rendering, Rails PrerenderError, and browser fetch paths. Fixes #3182. #3463 by justin808.

  • [Pro] react_on_rails:doctor renderer-cache scan now covers Jenkinsfile: The deprecated-task scan that flags react_on_rails_pro:pre_stage_bundle_for_node_renderer now also checks Jenkinsfile, alongside the existing CI/CD manifests and deploy scripts. Fixes #3269. #3442 by justin808.

  • Client-only Vite setups no longer fail Rails boot on Shakapacker's packageManager guard: React on Rails now installs an engine initializer that runs before shakapacker.manager_checker and no-ops Shakapacker's error_unless_package_manager_is_obvious! when the host app has no Shakapacker config (config/shakapacker.yml or SHAKAPACKER_CONFIG). This unblocks apps that use the react-on-rails/client npm package from an existing Vite entrypoint and do not use the Ruby render helpers. The Ruby helpers that resolve bundle paths still require Shakapacker configuration. Apps with Shakapacker config keep Shakapacker's guard unchanged. Fixes #3145. #3365 by justin808.

  • [Pro] HTTP rolling-deploy bundle responses now include stronger no-cache headers: The built-in rolling-deploy manifest and bundle endpoints now send Pragma: no-cache and X-Content-Type-Options: nosniff alongside Cache-Control: no-store, reducing the risk of legacy caching or MIME-sniffing mishandling authenticated bundle payloads. #3439 by justin808.

  • [Pro] OpenTelemetry shutdown timeout warning never logged: shutdownProviderWithTimeout in the Node Renderer's OpenTelemetry integration was missing a log.warn( call, leaving a bare string literal that produced no diagnostic when provider.shutdown() exceeded its timeout (and broke the source file's compilation). The timeout message now logs correctly. Follow-up to #3382. #3420 by justin808.

  • [Pro] HTTP rolling-deploy adapter now streams bundle downloads with a compressed-size cap: ReactOnRailsPro::RollingDeployAdapters::Http writes GET /bundles/:hash success responses to a Tempfile with a 50 MB compressed-body limit before extraction, and drains non-success responses through the same cap so oversized error bodies cannot exhaust heap. The existing 200 MB uncompressed tarball cap remains in place. Fixes #3416. #3435 by justin808.

  • bin/dev and doctor now label live reload defaults correctly: bin/dev help and react_on_rails:doctor now read config/shakapacker.yml before describing the default Procfile.dev mode, so apps using Shakapacker's live reload default are labeled as live reload instead of HMR. Doctor also treats Shakapacker's hmr: only mode as HMR when deciding whether to show HMR-specific warnings. Dev-server mode detection accepts both unquoted YAML booleans (hmr: true) and their quoted, case-insensitive equivalents (hmr: "true", hmr: "TRUE"), so apps with the quoted-boolean YAML anti-pattern still get correct labels and HMR-specific doctor warnings. Other non-boolean values (integers, arbitrary strings besides "only") and configs without an explicit dev_server block fall back to Shakapacker's live reload default for help text. Apps that keep HMR settings only under top-level default.dev_server should move them under development.dev_server or inherit them with a YAML anchor if they want HMR-specific doctor warnings. Custom SHAKAPACKER_CONFIG paths and symbol-valued YAML settings are respected during mode detection. Fixes #3374. #3377 by justin808.

  • [Pro] Streaming server-render responses now raise ReactOnRailsPro::Error when the stream response status is unavailable or the renderer delivers a readable HTTP error status as a streaming body, instead of silently returning no chunks. This is a user-visible behavior change for callers that do not already rescue ReactOnRailsPro::Error from each_chunk. #3383.

  • [Pro] TanStack Router hydration now supports the current router stores API: react-on-rails-pro/tanstack-router client hydration now uses TanStack Router's current router.stores.setMatches() API when router.__store.setState() is unavailable, so SSR hydration works with newer @tanstack/react-router releases without app-level compatibility shims. Fixes #3375. #3376 by justin808.

  • [Pro] TanStack Router hydration no longer double-calls loadRouteChunk under React 18 StrictMode: React 18's StrictMode double-renders components with fresh hook state on each pass, so the routerRef.current === null guard in clientHydrate.ts fired twice when options.createRouter returned the same router instance, re-running loadRouteChunk, __store.setState, and the user-defined hydrate callback. The render-phase init is now memoized via a module-level WeakMap keyed on the router instance, dedup'ing per-router side effects across mount cycles. Production behavior is unchanged because each mount creates a fresh router. Fixes #3405. #3410 by justin808.

  • [Pro] Benchmark CI starts the production dummy app on the expected port: react_on_rails_pro/spec/dummy/bin/prod now sets PORT=3001 by default before launching Foreman, preventing Foreman's default PORT=5000 from making the Rails server miss the benchmark workflow readiness check on localhost:3001. Both react_on_rails/spec/dummy/bin/prod and react_on_rails_pro/spec/dummy/bin/prod respect PORT when it's set. #3403 by alexeyr-ci2.

  • CI fails on stale lockfiles outside minimum-dependency jobs: GitHub Actions now runs Bundler with frozen lockfiles for standard integration, Pro, Playwright, lint, and precompile jobs, and no longer mutates lockfiles with bundle lock --add-platform. pnpm install is also frozen in Playwright. The intentionally mutable minimum-dependency jobs still use non-frozen installs after script/convert. #3404, #3430 by alexeyr-ci2.

  • [Pro] Generated bin/dev Procfiles now start the Node Renderer: Pro setup now appends a node-renderer process to Procfile.dev, Procfile.dev-static-assets, and Procfile.dev-prod-assets when those files exist, so SSR pages work in bin/dev, bin/dev static, and bin/dev prod. react_on_rails:doctor now warns when a Pro NodeRenderer app's launcher Procfiles can serve Rails pages but do not start a renderer on RENDERER_PORT. Fixes #3372. #3381 by justin808.

  • Install generator preserves explicit version pins when package-manager install fails: The install generator's add_packages path now writes versioned name@version specs directly into package.json (under dependencies or devDependencies) as a last-resort fallback when neither the primary nor fallback package manager install succeeds, so users can rerun their package manager manually without losing the pins. Specs without an explicit version are not written. #3387 by justin808.

  • [Pro] RSC client manifest restored when only registerServerComponent/client is in the pack graph: wrapServerComponentRenderer/client now directly imports react-on-rails-rsc/client.browser as a side-effect import. Previously the client runtime was only reachable through a three-level transitive chain (wrapServerComponentRenderer/clientgetReactServerComponent.clientreact-on-rails-rsc/client.browser). Tooling that severed any link in that chain (tree-shaking, transpiler quirks, custom NormalModuleReplacement, externals) caused RSCWebpackPlugin to emit Client runtime at react-on-rails-rsc/client was not found. React Server Components module map file react-client-manifest.json was not created. and silently skip the manifest, breaking RSC hydration on the Pro Node Renderer. The direct import keeps the runtime resource in the module graph so the plugin always emits react-client-manifest.json. Fixes #3366. #3368 by justin808.

  • [Pro] Updated Fastify in the Node Renderer for CVE-2026-33806: Raised the direct fastify dependency to 5.8.5 so user-provided Fastify server options, including trustProxy, pick up the upstream security fix. #3152 by dependabot[bot].

  • [Pro] TanStack Router hydration no longer bails to a full client re-render: TanStack Router SSR pages no longer discard server-rendered HTML during hydration because the client tree now renders RouterProvider with the same shape as the server output. Post-hydration navigation still waits for matched lazy route chunks before router.load(). #3213 by Seifeldin7.

  • [Pro] Widened ruby-jwt support to jwt >= 2.7: React on Rails Pro relaxes the previous ~> 2.7 cap to jwt >= 2.7, so applications can resolve the patched ruby-jwt 3.2.0+ release for the empty-key HMAC advisory while apps still on jwt 2.x remain compatible. #3322, #3344 by ihabadham.

  • [Pro] Pro migration generator rewrites all base-package references and preserves Gemfile pins: rails generate react_on_rails:pro now rewrites Jest/Vitest mock helpers (jest.mock, vi.mock, requireActual/importActual, and the rest) and TypeScript declare module 'react-on-rails' blocks alongside its existing import/require/dynamic-import handling, and the Gemfile swap now preserves the user's existing version pin (and other gem options) instead of overwriting them with the running gem's version. react_on_rails:doctor is widened to match: it also flags stale side-effect imports (import 'react-on-rails';), Jest/Vitest mock helpers, and declare module blocks, and the new side-effect-import pattern keeps the doctor a superset of the rewriter so anything the rewriter doesn't reach gets surfaced. Closes #3104. #3232 by justin808.

  • [Pro] Pro migration scans TypeScript 4.7 .mts and .cts modules: react_on_rails:doctor and the Pro migration rewriter now include .mts/.cts source files (and their .d.mts/.d.cts declaration counterparts) when looking for stale react-on-rails references, matching the existing .mjs/.cjs coverage. Fixes #3250. #3334 by justin808.

  • Doctor now honors nested JavaScript package roots: react_on_rails:doctor now checks package-manager lockfiles, package.json, and installed React from the configured node_modules_location, reducing false diagnostics for legacy apps that keep dependencies under client/. The Vite migration guide now documents the supported thin-wrapper pattern for those layouts. Note: a missing package.json at the configured node_modules_location now emits a warning instead of being silently skipped, so apps misconfigured against a nonexistent path will see new diagnostics on upgrade. Fixes #3205. #3220 by justin808.

  • Generated pack regeneration is now serialized: generate_packs_if_stale now uses a Rails tmp/ lock file, re-checks staleness after waiting, and avoids concurrent cleanup/regeneration races when multiple processes trigger auto-bundling at the same time. Fixes #1627. #3231 by justin808.

  • Install generator validates the selected JavaScript package manager: The install generator now checks the manager selected from REACT_ON_RAILS_PACKAGE_MANAGER, the packageManager field in package.json, or a lockfile on disk — instead of passing when any JavaScript package manager is installed. When the selected command is missing, the error names the selected manager, the source that selected it, and the available alternatives. The generator also warns when REACT_ON_RAILS_PACKAGE_MANAGER is set to a value outside the supported set (npm, pnpm, yarn, bun). Addresses package manager validation from #1958. #3229 by justin808.

  • Server-render error wrapping preserves original causes: When server rendering catches a non-Error thrown value, React on Rails now wraps it with the original value attached as cause, making downstream debugging preserve more context. Fixes #1746. #3230 by justin808.

  • bin/dev now cleans copied runtime files before startup: When you duplicate an app directory to run another local dev stack, bin/dev now removes copied stale Overmind sockets and stale tmp/pids/server.pid files that point to a Puma process running from another app directory. This prevents false startup failures in copied workspaces while still preserving active local sockets and pid files for the current app. #3142 by justin808.

  • bin/dev kill is more thorough and Pro-aware under base-port mode: ServerManager.kill_processes no longer short-circuits after the first successful step — pattern-based kills, port-based kills, and socket/pid cleanup all run unconditionally so a stale renderer port-binding or socket file cannot survive a bin/dev kill. In base-port mode, the derived renderer port (base+2) is now always included in port-based killing when react_on_rails_pro is loaded, even if RENDERER_PORT / REACT_RENDERER_URL are unset in the current shell (an informational message is printed so the wider scan is not silent). ProcessManager also now preserves the legacy RENDERER_URL env var alongside REACT_RENDERER_URL across Bundler's env reset so mid-migration users keep a consistent renderer URL in spawned subprocesses. #3274 by justin808.

  • [Pro] RSC setup now scopes client-reference discovery to app source: Generated RSC webpack configs now pass clientReferences based on Shakapacker's source_path, avoiding CI failures where the plugin could scan vendored gem templates under vendor/bundle. Fixes #3201. #3219 by justin808.

  • [Pro] Node renderer now exposes performance when supportModules: true: React 19's development build of React.lazy calls performance.now(), which previously threw ReferenceError: performance is not defined inside the node renderer's VM context unless users manually added performance via additionalContext. performance is now included in the default globals alongside Buffer, process, etc. Fixes #3154. #3158 by justin808.

  • Scaffolded CI workflow pins a pnpm version when packageManager is absent: The generated .github/workflows/ci.yml now emits with: version: for pnpm/action-setup@v4 when pnpm is detected from pnpm-lock.yaml alone, preventing the setup step from failing before dependency install. When packageManager is declared in package.json, the version key is omitted so the action reads the pin from there. Note: GeneratorMessages.detect_package_manager(package_json: nil) now treats nil as "caller cached that the file is absent" and skips disk fallback, instead of re-reading package.json; the previous fallthrough behavior is now the default (omit the keyword) and is documented on read_package_json. Fixes #3172. #3174 by justin808.

  • Client startup now recovers if initialization begins during interactive after DOMContentLoaded already fired: React on Rails now still initializes the page when the client bundle starts in the browser timing window after DOMContentLoaded but before the document reaches complete. Fixes #3150. #3151 by ihabadham.

  • Doctor accepts TypeScript server bundle entrypoints: react_on_rails:doctor now resolves common source entrypoint suffixes (.js, .jsx, .ts, .tsx, .mjs, .cjs) before warning that the server bundle is missing, preventing false positives when apps use server-bundle.ts. #3111 by justin808.

  • Doctor no longer fails custom projects for a missing generated bin/dev: react_on_rails:doctor now downgrades a missing official React on Rails bin/dev launcher from an error to a warning and adds explicit guidance when a custom ./dev script is detected, so custom projects can pass diagnostics when their development setup is intentional. Fixes #3103. #3117 by justin808.

  • [Pro] Reduced react-on-rails-pro-node-renderer published package size: added a files whitelist to package.json so pnpm pack no longer includes src/, tests/ fixtures, *.map, and lib/tsconfig.tsbuildinfo — matching the convention used by the sibling packages. Also marked react_on_rails_pro/spec/dummy as private so it can never be accidentally published. #3304 by alexeyr-ci2.

  • [Pro] HTTPX bidirectional streaming reliability: Fixed streaming request timeouts when using HTTPX with both the :stream and :stream_bidi plugins. The request now uses the build_request pattern with an explicit request.close so the HTTP/2 END_STREAM flag is sent, and a temporary monkey-patch (httpx_stream_bidi_patch.rb) works around an upstream :stream_bidi retry bug that left stale body callbacks registered and crashed retried requests with protocol_error. The patch is scoped and will be removed once fixed upstream. #2903 by AbanoubGhadban.

  • [Pro] Progressive RSC streaming flush granularity: RSC streaming now flushes on React's per-render-cycle flush() signal instead of setTimeout(flush, 0), so the shell and each resolved <Suspense> boundary stream as separate chunks rather than being merged into one large first message. This restores progressive streaming (and fixes worse-than-SSR First Contentful Paint) on pages with fast queries, and eliminates partial-HTML-tag chunks. Fixes #3194. #2903 by AbanoubGhadban.

  • [Pro] Node renderer graceful shutdown after stream timeouts: Fixed workers taking 30+ seconds to shut down after a StreamChunkTimeoutError during streaming. handleGracefulShutdown now also decrements the active-request count on onRequestAbort/onTimeout, the PassThrough wrapper is destroyed when the source render stream errors, and the HTTP response is closed on chunk timeout so connections to Rails no longer hang. Fixes #2270 and #2308. #2903 by AbanoubGhadban.

Deprecated

  • [Pro] config.renderer_http_keep_alive_timeout is deprecated: The setting now has no effect because async-http manages renderer client lifecycle through scheduler-scoped clients when a Fiber.scheduler is already running and per-request clients otherwise. Explicitly setting it to a non-nil value emits a deprecation warning; leaving it unset or setting it to nil is accepted silently. Remove non-nil configuration during upgrade. See docs/pro/updating.md for the full upgrade guide. #3320 by AbanoubGhadban.

Removed

  • [Pro] Removed the legacy license key-file migration warning: The elapsed migration notices for the legacy config/react_on_rails_pro_license.key file path (the cleanup notice shown alongside a valid configured license, and the missing-license migration notice) are no longer emitted. The legacy file itself was already unread. Fixes #3624. #3715 by justin808.
  • [Pro] Removed HTTPX transport gem dependencies from the Node Renderer: React on Rails Pro no longer depends on httpx, http-2, or connection_pool after migrating to async-http. Applications that directly pin or require those gems for renderer integration should remove that coupling or add their own explicit dependency. #3320 by AbanoubGhadban.
  • [Pro] Removed the --rsc-pro install generator flag: --rsc already implies Pro, so the separate mode was unnecessary. Behaviors previously gated on --rsc-pro (Pro verification checklist, prerelease install note, exact Pro gem pin on prereleases) now fire on --rsc installs. See also #3104, which tracks unrelated silent-failure bugs in the Pro upgrade automation. #3105 by ihabadham.

Security

  • [Pro] Hardened Node Renderer password lifecycle: The protocol-mismatch (412) response no longer echoes the request body verbatim — only non-sensitive field names are returned, so a mismatched password is never reflected back. The Pro install generator now provisions a random 64-hex-character renderer password instead of the publicly-known devPassword default, and production startup now logs a non-blocking warning for known-weak or short (< 16 character) renderer passwords. Fixes #3397. #3399 by AbanoubGhadban.

Don't miss a new react_on_rails release

NewReleases is sending notifications on new releases.