Skip to content

v17.1.0.rc.0

Pre-release
Pre-release

Choose a tag to compare

@justin808 justin808 released this 26 Aug 20:44
· 12 commits to main since this release
5db45d7

Fixed

  • [Pro] Expected Node Renderer cold starts no longer emit OpenTelemetry error spans: The
    ror.bundle.build_execution_context cache-first probe previously ended with status ERROR when a worker had not
    compiled a bundle's VM context yet, even though the normal cache-miss path then rendered successfully. The probe
    now treats that expected miss as normal control flow while preserving error spans for genuine failures.
    #4908 by
    sashakhar1.

  • [Pro] Hydrated Redux stores no longer leak across Turbo/Turbolinks navigations: The hydrated-store
    registry is now cleared on client-side page unload (soft navigation), alongside the existing component
    teardown. Previously a leftover entry from the previous page made getOrWaitForStore resolve immediately
    with the previous page's store, silently defeating the store_dependencies hydration gate — most visibly
    with deferred stores (redux_store(..., defer: true)), where a mid-page component could render the
    previous page's Redux state instead of waiting for its own page's hydration data. Registered store
    generators are unaffected and persist across navigations. Note for apps that relied on a store surviving a
    soft navigation without re-rendering its hydration data: each page using a store must now render its own
    redux_store call (the documented model). Fixes
    #4861.
    #4871 by
    AbanoubGhadban.

  • On-demand renders initialize only the island's declared store dependencies:
    reactOnRailsComponentLoaded walked every store element on the page even when asked to render a
    single component, so an unrelated store element whose generator was not registered (e.g. its
    bundle had not loaded yet) aborted the requested render, and unrelated new stores were hydrated
    as a side effect. It now reads the data-store-dependencies attribute the react_component
    helper already emits (defaulted to the stores registered in the request, or set explicitly with
    the store_dependencies option) and initializes exactly those stores, matching the Pro
    renderer's dependency gating; a declared dependency that cannot initialize is logged without
    aborting the render. Markup without the attribute keeps the previous initialize-every-store
    behavior, and reactOnRailsPageLoaded/full page loads are unchanged. Completes
    #4862.
    #4870 by
    AbanoubGhadban.

  • [Pro] Rails requests to the Node Renderer once again continue OpenTelemetry traces: The async-http transport
    now creates a CLIENT span and injects W3C trace context for regular and streaming renders, incremental async-props
    renders, raw-render requests, and asset uploads when the Rails application has configured the OpenTelemetry SDK.
    OpenTelemetry remains optional, and spans record only the HTTP method, normalized request path, response status, and
    request/response byte sizes. Fixes
    #4866.
    #4869 by
    sashakhar1.

  • [Pro] Node Renderer transport follow-ups now expose protocol errors and accurate Fastify modes:
    Rails retries continue for network disconnects and peer-reset HTTP/2 streams, while HTTP parser and framing errors
    surface directly. Public configureFastify callbacks and fastifyServerOptions now reflect both HTTP/1.1 and
    HTTP/2 runtime modes.
    #4893 by
    sashakhar1.

  • [Pro] Bounded Node Renderer VM retention now avoids old/new RSC rebuild thrash during rolling deploys:
    The default per-worker VM hard cap now retains four contexts, enough for the server and RSC bundles from one
    draining and one current revision. Successful bundle sets remain reusable through a configurable, timer-driven
    drain window, while inactive contexts, generation metadata, and pressure logs stay bounded. Pre-seeding now emits
    an immutable revision-scoped current-generation declaration; each configured renderer worker validates and compiles
    that complete server/RSC set before listening, pins it across old-only traffic gaps, and reports ready only after the
    compile barrier. Symlink-mode cache paths reuse the validated immutable snapshot VM identity without rebuilding on
    the first request. Doctor now requires valid renderer JavaScript before launcher-derived capacity can prove a warm
    pass, and reports observed/unverified declaration evidence rather than claiming success from invalid syntax,
    loopback, or Rails-process configuration. Upgrade memory impact: the
    default maxVMPoolSize doubles from 2 to 4 per worker, and total VM retention scales with renderer workers and
    replicas, so operators should re-check deployment memory requests and limits. Invalid MAX_VM_POOL_SIZE values
    that previously fell back to the default now fail fast during renderer startup. Fixes
    #4810.
    #4811 by
    justin808.

  • Routine startup diagnostics no longer appear in default INFO logs: Successful package validation, valid
    Pro license checks, non-production missing-license notices, and Node renderer connection setup now log at DEBUG
    instead of INFO. Package validation still runs, while expired or invalid configured licenses remain visible outside
    production, and production license warnings and renderer failures remain actionable. Fixes
    #4848.
    #4849 by
    Justin Gordon.

  • Generated server webpack configs no longer include an unused merge import or stale comments:
    commonWebpackConfig already clones the shared client configuration, so the generated server configuration
    stays lint-clean without changing its runtime behavior. Fixes
    #4791.
    #4840 by
    ihabadham.

  • Release retries now durably reuse maintainer-verified ShakaPerf evidence: rake release can bind an
    existing successful run to the canonical release tracker with RELEASE_SHAKAPERF_RUN, then re-fetch and
    re-verify the exact SHA or machine-proven runtime-equivalent evidence on later invocations instead of
    dispatching duplicate performance runs. Tracker trust is bound to the exact
    Release gate: react_on_rails X.Y.Z stable-base title, explicit selectors take precedence over automatic
    accepted-RC reuse, and persisted accelerated retries cannot silently consume a selector. Automatic reuse
    falls back to normal discovery only for authoritative natural invalidation such as stale or missing evidence,
    a failed/cancelled live run, or proven runtime divergence; indeterminate API or Git failures, detector errors,
    and record mutations remain blocking. Raw REST-selected runs now preserve their creation timestamp through
    strict-final verification. Before either a live release or dry run can reach registry checks, confirmation,
    mutation, tagging, or publication, the task also verifies the frozen pnpm install state, the repository-pinned
    pnpm version, and lifecycle-enabled builds of all four npm release packages before creating a release checkout,
    pulling, authenticating, or reading any remote release state. The successful check is bound to the exact commit;
    when a live git pull --rebase advances HEAD, the task rebuilds and rebinds readiness before resolving the release
    version or continuing. npm publication now retries only explicit OTP challenges and context-qualified transient
    network/HTTP failures; successful lifecycle/tool banners no longer mask those diagnostics, while authentication,
    actual lifecycle failures, registry rejection, incidental numeric diagnostics, and unknown failures stop immediately
    with OTP values redacted. Saved ShakaPerf evidence also recognizes GitHub CLI's exact
    no valid artifacts found to download diagnostic as authoritative absence while preserving observation failures as
    blocking. Stable releases also support
    an append-only, tracker-bound schema-v2 observation waiver bound to the exact run attempt and canonical
    repository/workflow/event/branch identity. Legacy schema-v1 waiver markers remain readable audit history but cannot
    authorize publication. The waiver does not
    claim the run succeeded or bypass any other release gate; the tracker, waiver, and exact run are revalidated
    before remote tag push and package publication, and a rerun attempt blocks both boundaries. Fixes
    #4812.
    #4833 by
    justin808.

  • [Pro] RSC render-error details no longer reach browser-facing payloads in production-like
    environments
    : Fetched RSC payload metadata now uses a fail-closed allowlist that exposes only the
    generic hasErrors signal needed by client error boundaries. Inline error-bearing payload chunks now
    also suppress console replay in production, staging, and unrecognized environments, closing a path
    that could repeat the server error message or source-mapped file paths after diagnostic metadata was
    redacted. Full diagnostics and console replay remain available in development and test, clean
    production chunks retain console replay, and server-side reporting still receives the original error
    details before the browser-boundary redaction runs. Fixes
    #4736,
    #4822, and
    #4827.
    #4821 and
    #4856 by
    justin808.

  • HTTP-served SSR bundle loading now honors the response charset, rejects non-2xx responses,
    and no longer leaks URL credentials into error messages
    :
    When server_bundle_js_file resolves to an HTTP(S) URL, RubyEmbeddedJavaScript.file_url_to_string
    no longer assumes the Content-Type header always ends in an exact ; charset=... form. A response
    with no charset, a Content-Type with no charset parameter, a missing Content-Type header, or a
    quoted charset value (charset="ISO-8859-1") now transcodes to UTF-8 successfully instead of
    raising or silently mislabeling the bytes, falling back to UTF-8 when no usable charset is declared.
    A non-2xx response (for example a 404 page or a proxy error) is now rejected with a clear
    bundle-load error naming the URL and status, instead of being returned as if it were JavaScript
    bundle source. Additionally, a server_bundle_js_file URL with embedded HTTP basic-auth
    credentials (e.g. https://:password@host:3800/bundle.js) no longer leaks that password into
    raised errors or logs on a load failure. Fixes
    #4584.
    #4817 by
    justin808.

  • RailsContext now stays current across Turbo and Turbolinks navigation: Parsed context is cached
    only while its source element and JSON text remain unchanged. Replacing the context element or
    morphing its payload in place now makes the next core render or immediate Pro hydration receive the
    new page's URL, locale, and application-specific values instead of the first page's context. Fixes
    #4583.
    #4765 by
    ihabadham.

  • Stopped replacing Shakapacker-owned watch binstubs during installation: React on Rails no longer ships
    its own bin/shakapacker-watch template. Generated Procfiles use Shakapacker's optional watch binstub when
    it is present and fall back to bin/shakapacker --watch for older supported Shakapacker installations.
    Existing Shakapacker-provided or customized watch binstubs remain untouched, including under --force. Fixes
    #4617.
    #4715 by
    ihabadham.

  • Server rendering no longer crashes on or corrupts lone UTF-16 surrogates: When the JavaScript
    renderer emits a string containing a lone surrogate (commonly from truncating text mid-emoji, e.g. an
    excerpt cut with slice/substring), JSON.stringify serializes it as a \uXXXX escape that Ruby's
    JSON.parse mishandles — depending on the case and json gem version it raises incomplete surrogate pair, returns invalid UTF-8, or silently mis-decodes the value. The parser now normalizes such escapes to
    the Unicode replacement character () and passes the content through to the browser instead of failing.
    This works the same across json gem versions. Clean payloads are unaffected: repair only runs when the
    text actually contains a surrogate escape, a single cheap substring check. Fixes
    #4710.
    #4745 by
    AbanoubGhadban.

  • [Pro] Streaming caches no longer persist error-containing renders: When a streamed render
    emits a chunk with hasErrors: true (for example a Suspense boundary whose async data fetch hit a
    transient failure on that one request), the resulting chunk set is no longer written to
    Rails.cache. Under production defaults (raise_non_shell_server_rendering_errors: false), a stream
    whose shell rendered but whose async boundary errored completes "normally", so both stream cache-write
    paths (ReactOnRailsPro::StreamCache for prerender caching and cached_stream_react_component's
    view-level cache) previously persisted the broken fragment and served it from cache to every
    subsequent visitor on that key until the entry expired — turning a single transient failure into a
    persistent outage for that component. Both paths now detect the error chunk and skip the cache write;
    clean renders are still cached. Fixes
    #4581.
    #4722 by
    justin808.

  • [Pro] Buffered and static RSC caches no longer persist error-containing renders: Buffered
    component HTML and static RSC HTML now skip cache writes when any streamed chunk reports
    hasErrors: true; clean renders remain cacheable. Fixes
    #4723.
    #4804 by
    justin808.

  • renderComponent now handles DOM IDs containing selector metacharacters: Component payloads are
    matched by their exact data-dom-id attribute value, so valid IDs containing quotes, backslashes,
    hashes, periods, or brackets no longer throw selector errors or fail to render. Fixes
    #4585.
    #4808 by
    justin808.

  • Stopped Doctor from warning when a layout uses only one pack helper: rake react_on_rails:doctor
    no longer emits ⚠️ <layout>: has javascript_pack_tag but missing stylesheet_pack_tag (or its mirror)
    based purely on helper asymmetry. A JavaScript-only Shakapacker entrypoint, a CSS-only pack, and a
    hybrid layout that loads its CSS through Propshaft stylesheet_link_tag are all valid, so detected pack
    helpers are now reported informationally instead of as warnings. A fresh create-react-on-rails Rails 8
    RSC app no longer receives a spurious layout warning. Fixes
    #4619.
    #4724 by
    justin808.

  • [Pro] Stopped logging routine async-props stream-close races at error level: When a client
    disconnects mid-render, or a stream_react_component_with_async_props block keeps emitting after
    renderComplete winds the connection down, writes to the already-closed renderer request stream are
    now treated as routine disconnects: logged once at debug (no backtrace, gated on logging_on_server)
    and short-circuited so the block's remaining props are skipped silently. Previously every remaining
    prop produced an error-level entry with a 5-frame backtrace, so a single disconnect spammed logs in
    proportion to the number of props still in flight and buried genuine emit failures in the noise.
    Genuine write failures (for example a JSON::GeneratorError) still log at error with a backtrace.
    This mirrors the sibling ReactOnRailsPro::Stream#log_client_disconnect convention. Fixes
    #4325.
    #4719 by
    justin808.

  • [Pro] Stopped retaining both raw and parsed source maps per pooled Node renderer VM: Once a
    bundle's source map is parsed on first use, the raw map JSON (up to 50MB under the cap) is released and
    the parsed map becomes the VM generation's single retained copy. For inline (data:) maps the encoded
    URL payload (larger than the raw JSON) is released as well, so the single-copy reduction applies to both
    external and inline maps. A registration whose raw map was released never falls back to re-reading the
    map from disk, preserving the same-path rebuild guarantee: old VM generations keep remapping through
    their own map even after the on-disk map is overwritten. Also makes an unusable inline (data:) source
    map terminal on the lazy lookup path instead of retried, matching registration-time behavior. Partially
    addresses
    #4313.
    #4711 by
    justin808.

  • [Pro] Made Node renderer cache identities cover complete artifacts and hardened multi-origin rolling deploys:
    Renderer IDs now bind the server/RSC role, bundle bytes, and the final companion-file names and bytes, so
    manifest-only and loadable-stats.json changes invalidate fragment, upload, pre-seed, and runtime caches together.
    The built-in HTTP adapter accepts ordered plural previous URLs, normalizes bare origins through the configured mount,
    preserves explicit paths, rejects ambiguous legacy hashes across origins, verifies every v2 payload before staging,
    and enforces monotonic operation deadlines. Fixes
    #4620.
    #4701 by
    justin808.

  • [Pro] Capped the size of external source maps read by the Node renderer: External .map files
    whose on-disk size exceeds 50MB are skipped by a pre-read size gate rather than read into memory (this is
    not a hard memory bound: a map that grows between the size check and the read is still read in full).
    Previously only inline (data:) source maps were
    size-capped, so a large external map was read and retained for the life of each pooled VM. Behavior change:
    stack frames for a bundle whose external map exceeds the cap keep their bundled locations instead of being
    remapped to original sources, and the renderer logs a warning naming the map. An oversized map is never
    substituted: the renderer will not fall back to a differently-named map alongside the bundle. When the
    oversized map is the one the bundle names, that result is final for the VM generation and is not retried;
    a map that merely arrives late is still retried as before. The 50MB limit matches the pre-existing cap for
    inline maps. Partially addresses
    #4313.
    #4688 by
    AbanoubGhadban.

  • [Pro] Bounded RSC browser performance fallback marks across soft navigations: Browsers
    that cannot preserve PerformanceMark.detail now retain only the 200 most recent fallback
    entries, and React on Rails Pro clears the fallback queue during Turbo/Turbolinks page teardown.
    The TypeScript runtime, generated inline helper, and Ruby streaming mirror share the same limit.
    Fixes #4329.
    #4690 by
    justin808.

  • Redacted sensitive server-render error context and tightened Ruby request helpers: Server-render
    exceptions no longer retain raw component props, generated JavaScript, or renderer parse details in log and
    error-tracker context.
    Development service checks now use safe YAML loading, and Pro renderer asset queries encode filenames.
    Fixes #4597.
    #4624 by
    justin808.

  • [Pro] RSC render errors no longer leak server details into client DOM: In production, the
    REACT_ON_RAILS_RSC_ERRORS inline script now emits only { hasErrors: true } — the full error
    message, stack trace, and file paths are redacted. Uses an allowlist (development/test show full
    diagnostics) so custom envs like staging default to redacted. RSC rendering errors are now also
    reported to error reporters (Sentry/Honeybadger) even with the default throwJsErrors: false
    config, via a custom 'renderingError' stream event. Fixes
    #4629.
    #4631 by
    AbanoubGhadban.

  • [Pro] Hardened Node renderer authentication and multipart uploads: Authenticated multipart
    clients must now send the password field before file parts; the renderer rejects and discards leading file
    parts before creating upload storage. Uploads now enforce a 100 MB total request limit across files, fields,
    and multipart framing, plus a 1,000-part cap and a matching per-file cap. Password comparisons use fixed-length
    digests to avoid leaking the configured password length. Fixes
    #4596.
    #4623 by
    justin808.

  • [Pro] RSC migration diagnostics require graph-derived client references: The RSC generator's
    doctor now reports custom or legacy clientReferences values as missing the generated
    manifest-backed resolver, and the migration guide shows how to print the canonical CommonJS
    resolver and run it in custom ESM webpack configs with a tested compatibility prelude. Fixes
    #4609.
    #4625 by
    justin808.

  • [Pro] Streamed RSC roots hydrate without transport-node mismatches: Pro client hydration now
    removes the embedded RSC payload initializer from the hydration root, relocates leading streamed
    RSC resource tags out of the root before React attaches, and wraps the default RSC provider path in
    the same null Suspense boundary used by the server stream. This prevents recoverable hydration
    mismatches on streamed RSC apps such as the flagship demo. Fixes
    #4525. #4532 by justin808.

  • [Pro] Healthy incremental streams no longer log premature close-hook timeout warnings: The
    Pro Node renderer now suppresses the short close-hook timeout warning while an incremental response
    is still open and finishing normally, while preserving the diagnostic when the close hook remains
    stalled after the response finishes. Fixes
    #4524.
    #4531 by
    justin808.

Added

  • [Pro] Node Renderer OpenTelemetry initialization now composes with application observability stacks:
    Applications can append custom instrumentations to the built-in HTTP and Fastify pair, merge resource-detector
    attributes below explicit resource configuration, or opt in to preserving renderer ror.* spans through an
    application-owned global provider. Empty service-name values from environment variables, options, and resource
    attributes are treated as unset. Renderer-managed shutdown disables registered instrumentations and shuts down
    provider components after successful initialization, while failed initialization preserves caller-supplied processors
    and exporters.
    Fixes #4867.
    #4878 by
    sashakhar1.

  • [Pro] HTTP/1.1 is now a supported Node Renderer transport for load balancers and HTTP/1.1-only probes:
    Rails can opt out of forcing h2c for cleartext renderer URLs with
    config.renderer_http_force_http2 = false, paired with fastifyServerOptions: { http2: false } on the Node
    Renderer. Regular rendering and response streaming continue to work. Async props require full-duplex behavior across
    every hop, so request-buffering or half-duplex HTTP/1.1 intermediaries remain unsupported. Fixes
    #4868.
    #4887 by
    sashakhar1.

  • Version-matched agent skills and bundled docs: The react_on_rails gem and
    react-on-rails npm package now ship install/upgrade, React Server Components adoption,
    streaming debugging, and doctor fix-loop skills with concise package-local references.
    Newly scaffolded AGENTS.md files point agents to the installed package content before
    the hosted docs, avoiding guidance drift across releases. Fixes
    #4605.
    #4809 by
    justin808.

  • [Pro] RSC agent guardrails installer: New
    rake react_on_rails:install_rsc_agent_guardrails task (also run automatically by the RSC
    generator) installs a Claude Code rsc-app-safety skill and an advisory hook into a host app's
    .claude/, steering AI coding agents away from React Server Components API footguns (mounting the
    RSC payload route without authentication, trusting server-component props, exposing the Node
    renderer, leaking secrets). Idempotent and safe to re-run after upgrades.
    #4606 by
    justin808.

  • Existing-app Pro choice: Interactive react_on_rails:install runs now ask whether to enable
    React on Rails Pro when no Pro, RSC, or standard-only choice is supplied. The bounded prompt defaults
    to yes and explains trust-based evaluation and production licensing, while noninteractive runs retain
    the open-source-only default. Explicit --pro, --no-pro, --rsc, --no-rsc, and --standard-only
    choices skip the prompt. The doctor and post-install guidance now show the Pro upgrade path when Pro is
    off. Fixes #4604 in
    #4615 by
    justin808.

  • Agent-legible doctor contract: bin/rails react_on_rails:doctor FORMAT=json now includes stable
    check IDs, explicit severity and documentation fields, a nullable field reserved for diagnosis-specific
    safe mechanical fix commands,
    and self-contained remediation prompts. The versioned schema, deterministic ordering, and exit-code
    behavior are documented for CI and coding-agent integrations. Fixes
    #4602.
    #4611 by
    justin808.

  • [Pro] Optional authorization callback for RSC payload routes: Apps can configure
    rsc_payload_authorizer to reject payload requests before component props are parsed or rendered.
    Existing behavior remains unchanged when the callback is unset, and the RSC security docs now explain
    the public endpoint boundary and app-controller authorization seam.
    Fixes #4595.
    #4621 by
    justin808.

  • [Pro] Configurable license-token secret sources: Rails applications can now provide a paid
    license through config.license_token, including from Rails credentials, while standalone Node
    renderers can use the licenseToken option. Explicit nonblank configuration takes precedence over
    REACT_ON_RAILS_PRO_LICENSE, blank configuration preserves the environment fallback, and renderer
    diagnostics mask token values. #4552 by
    ihabadham.

Changed

  • [Pro] Render requests now send raw JavaScript bodies to the Node renderer: Non-bundle render
    requests use a raw application/vnd.react-on-rails.render-request+javascript body with metadata in
    X-React-On-Rails-Pro-* headers instead of application/x-www-form-urlencoded, removing
    URL-encoding overhead on large rendering payloads. The renderer still accepts the legacy form
    encoding, so a not-yet-upgraded gem keeps working against an upgraded renderer during rolling
    deploys; deploy the Node renderer before or together with the gem upgrade, since an older renderer
    rejects the new content type. Fixes
    #3584.
    #4579 by
    alexeyr-ci2.