Skip to main content

React on Rails Pro Configuration

Pro Feature — Available with React on Rails Pro. Free or very low cost for startups and small companies. Upgrade or licensing details →

For general React on Rails configuration options, see Configuration.

config/initializers/react_on_rails_pro.rb

  1. You don't need to create an initializer if you are satisfied with the defaults as described below.
  2. Values beginning with renderer pertain only to using an external rendering server. You will need to ensure these values are consistent with your configuration for the external rendering server, as given in JS configuration
  3. config.prerender_caching works for standard mini_racer server rendering and using an external rendering server.

Example of Configuration

Also see spec/dummy/config/initializers/react_on_rails_pro.rb for how the testing app is setup.

The below example is a typical production setup, using the separate NodeRenderer, where development takes the defaults when the ENV values are not specified.

ReactOnRailsPro.configure do |config|
# If true, then capture timing of React on Rails Pro calls including server rendering and
# component rendering.
# Default for `tracing` is false.
config.tracing = true

# Array of globs to find any files for which changes should bust the fragment cache for
# cached_react_component and cached_react_component_hash. This should include any files used to
# generate the JSON props, webpack and/or webpacker configuration files, and npm package lockfiles.
# Default for `dependency_globs` is an empty array
config.dependency_globs = [ File.join(Rails.root, "app", "views", "**", "*.jbuilder") ]

# Array of globs to exclude from config.dependency_globs for ReactOnRailsPro cache key hashing
# Default for `excluded_dependency_globs` is an empty array
config.excluded_dependency_globs = [ File.join(Rails.root, "app", "views", "**", "dont_hash_this.jbuilder") ]

# Remote bundle caching saves deployment time by caching bundles.
# See /docs/oss/building-features/bundle-caching.md for usage and examples.
config.remote_bundle_cache_adapter = nil

# ALL OPTIONS BELOW ONLY APPLY IF SERVER RENDERING

# If true, then cache the evaluation of JS for prerendering using the standard Rails cache.
# Applies to all rendering engines.
# Default for `prerender_caching` is false.
config.prerender_caching = true

# Retry request in case of time out on the node-renderer side
# 5 - default, if not specified
# 0 - no retry
config.renderer_request_retry_limit = 5

# NodeRenderer is for a renderer that is stateless. It does not need restarting when the JS bundles
# are updated. It is the only custom renderer currently supported. Leave blank to use the standard
# mini_racer rendering. Other option is NodeRenderer
# Default for `server_renderer` is "ExecJS"
config.server_renderer = "NodeRenderer"

# React on Rails Node Renderer now support render functions returning promises! To enable this optional functionality,
# toggle the following option.
# Default is false.
config.rendering_returns_promises = false

# If you're using the NodeRenderer, a value of true allows errors to be thrown from the bundle
# code for SSR so that an error tracking system on the NodeRender can use the exceptions.
# If you are using ExecJS as your rendering method, set this to false.
# Default is true.
config.throw_js_errors = true

# You may provide a password and/or a port that will be sent to renderer for simple authentication.
# `https://:<password>@url:<port>`. For example: https://:YOUR_SECURE_PASSWORD@renderer:3800. Don't forget
# the leading `:` before the password. Your password must also not contain certain characters that
# would break calling URI(config.renderer_url). This includes: `@`, `#`, '/'.
# **Note:** Don't forget to set up **SSL** connection (https) otherwise password will useless
# since it will be easy to intercept it.
# If you provide an ENV value (maybe only for production) and there is no value, then you get the default.
# Default for `renderer_url` is "http://localhost:3800".
config.renderer_url = ENV["REACT_RENDERER_URL"]

# If you don't want to worry about special characters in your password within the url, use this config value
# Default for `renderer_password` is nil
# config.renderer_password = ENV["RENDERER_PASSWORD"]

# Set the `ssr_timeout` configuration so the Rails server will not wait more than this many seconds
# for a renderer socket read once issued. With the async-http renderer client, this is applied as
# the per-read socket timeout on the renderer connection. Increase this value for long-running
# streaming SSR responses with legitimate gaps between chunks.
config.ssr_timeout = 5

# If false, then crash if no backup rendering when the remote renderer is not available
# Can be useful to set to false in development or testing to make sure that the remote renderer
# works and any non-availability of the remote renderer does not just do ExecJS.
# Suggest setting this to false if the SSR JS code cannot run in ExecJS
# Default for `renderer_use_fallback_exec_js` is true.
config.renderer_use_fallback_exec_js = false

# Maximum number of concurrent async-http connections per client to the Node renderer.
# HTTP/2 may multiplex multiple request streams over those pooled connections.
# When a Fiber.scheduler already exists before the renderer request enters `Sync {}`, the
# client is reused across requests within that long-lived scheduler (persistent connection /
# keep-alive; see the tuning section below), so this limit bounds connection concurrency for
# streamed renders sharing one client.
# Otherwise the adapter uses a request-scoped client. Under standard Puma (no pre-existing scheduler), `Sync {}`
# creates that scheduler/client for the request and cleans it up when the request ends.
# See "Renderer Performance Tuning for Streamed RSC" below.
# Default for `renderer_http_pool_size` is 10
config.renderer_http_pool_size = 10

# TCP connect timeout in seconds. After the socket connects, request processing and streaming
# are bounded by `ssr_timeout`.
# Default for `renderer_http_pool_timeout` is 5
config.renderer_http_pool_timeout = 5

# warn_timeout - Displays a warning message if a request takes longer than the given time in seconds.
# Default is 0.25
config.renderer_http_pool_warn_timeout = 0.25 # seconds

# Snippet of JavaScript to be run right at the beginning of the server rendering process. The code
# to be executed must either be self contained or reference some globally exposed module.
# For example, suppose that we had to call `SomeLibrary.clearCache()`between every call to server
# renderer to ensure no leakage of state between calls. Note, SomeLibrary needs to be globally
# exposed in the server rendering webpack bundle. This code is visible in the tracing of the calls
# to do server rendering. Default is nil.
config.ssr_pre_hook_js = "SomeLibrary.clearCache();"

# When using the Node Renderer, you may require some extra assets in addition to the bundle.
# The assets_to_copy option allows the Node Renderer to have assets copied at the end of
# the assets:precompile task or directly by the
# react_on_rails_pro:copy_assets_to_remote_vm_renderer task.
# These assets are also transferred any time a new bundle is sent from Rails to the renderer.
# The value should be a file_path or an Array of file_paths. The files should have extensions
# to resolve the content types, such as "application/json".
config.assets_to_copy = [
Rails.root.join("public", "webpack", Rails.env, "loadable-stats.json"),
Rails.root.join("public", "webpack", Rails.env, "manifest.json")
]

################################################################################
# REACT SERVER COMPONENTS (RSC) CONFIGURATION
################################################################################

# Enable React Server Components support
# When enabled, React on Rails Pro will support RSC rendering and streaming
# Default is false
config.enable_rsc_support = true

# Path to the RSC bundle file (relative to webpack output directory or absolute path)
# The RSC bundle contains only server components and references to client components.
# It's generated using the RSC Webpack Loader which transforms client components into
# references. This bundle is specifically used for generating RSC payloads and is
# configured with the 'react-server' condition.
# Default is "rsc-bundle.js"
config.rsc_bundle_js_file = "rsc-bundle.js"

# Path to the React client manifest file (typically in your webpack output directory)
# This manifest contains mappings for client components that need hydration.
# It's automatically generated by the React Server Components Webpack plugin and is
# required for client-side hydration of components.
# Only set this if you've configured the plugin to use a different filename.
# Default is "react-client-manifest.json"
config.react_client_manifest_file = "react-client-manifest.json"

# Path to the React server-client manifest file (typically in your webpack output directory)
# This manifest is used during server-side rendering with RSC to properly resolve
# references between server and client components.
# It's automatically generated by the React Server Components Webpack plugin.
# Only set this if you've configured the plugin to use a different filename.
# Default is "react-server-client-manifest.json"
config.react_server_client_manifest_file = "react-server-client-manifest.json"

# These RSC configuration files are crucial when implementing React Server Components
# with streaming, which offers benefits like:
# - Reduced JavaScript bundle sizes
# - Faster page loading
# - Selective hydration of client components
# - Progressive rendering with Suspense boundaries
end

Renderer Performance Tuning for Streamed RSC

The dominant contributors to a streamed route's responseEnd tail are Node renderer round-trip overhead, cold or under-warmed renderer workers, and per-request connection setup between Rails and the renderer. Three levers address these (see issue #4240). Measure changes with before/after page-load timing for the streamed route, Server-Timing for the early Rails/renderer phases that are known before the first stream write, the inline RSC stream performance marks described in the Streaming SSR guide, and renderer logs or tracing for cold-worker behavior. Inline marks remain the source for late payload, flush, hydration, and stream-drain timing because ActionController::Live commits headers on the first stream write.

1. Warm up renderer workers

Each worker compiles its bundle on its first render request, so the first measured render after a deploy is cold. Pre-warm every worker before serving real traffic so cold-start cost does not land on a user (or skew a benchmark):

  • Send a warm-up render (or hit /ready after a warm-up render) to each replica during deploy.
  • With workersCount > 1, a single warm-up render is not enough. Route warm-up traffic so it reaches every worker under your actual load-balancing policy; sticky or hash-based routing may require replica-local hooks or an explicit fan-out endpoint. Do not gate all traffic on /ready without a separate warm-up path — if no render requests reach the renderer, /ready never flips to 200 (deadlock).

Full warm-up patterns (Kubernetes probes, postStart hooks, the /ready cold-start contract) are in Node renderer health checks → Gating traffic on /ready.

2. Size the worker pool and the connection pool together

Two independent limits gate renderer throughput:

SettingWhereDefaultGoverns
workersCount / RENDERER_WORKERS_COUNTRendererCPUs − 1How many renders the renderer can execute concurrently.
renderer_http_pool_sizeRails10Max concurrent async-http connections per client to the renderer.

Guidance:

  • With Falcon or another long-lived scheduler, keep renderer_http_pool_size close to (and generally not far above) workersCount; streamed renders sharing that scheduler use the same async-http client, and HTTP/2 may multiplex request streams over the pooled connections. Sending many more concurrent renderer requests than there are workers just queues renders at the renderer while adding connection and scheduling overhead.
  • Under standard Puma streaming, Sync {} creates a request-scoped client, so renderer_http_pool_size only bounds concurrent async-http connections inside one streamed response. Use a value near the number of renderer calls one response can overlap; scale workersCount and renderer replicas for cross-request concurrency.
  • Account for your Rails concurrency: with many Puma threads/workers all streaming, a renderer with only one or two workers becomes the bottleneck. Scale workersCount (and renderer replicas) to your real concurrent streamed-render load.
  • Tune ssr_timeout for legitimate long gaps between streamed chunks — it applies as a per-read socket timeout, so it fires when a single read from the renderer blocks for ssr_timeout seconds. It is not a total response-duration cap; avoid masking renderer hangs with an unnecessarily high value.

3. Rails ↔ renderer keep-alive (persistent on Falcon/async scheduler; per-request on standard Puma)

Connection reuse is automatic when the renderer request runs under a long-lived Fiber.scheduler, such as Falcon or Puma configured with an async scheduler. In that setup, the async-http client is stored on the scheduler and reused across streaming requests, so HTTP/2 connections stay alive and renders multiplex over them instead of paying TCP handshake and H2 connection setup per request (issue #3283). No React on Rails configuration is required to enable this.

Under standard Puma, the streaming helper's Sync {} block creates a per-request scheduler. The async-http client is cleaned up when that streaming response ends, so connection reuse does not persist across consecutive Rails requests. The benefit is still meaningful inside a single streamed response: renderer calls in that response can share the same client lifecycle and renderer_http_pool_size still bounds concurrent async-http connections within that request.

Call the renderer from the normal Rails request path. The adapter chooses scheduler-scoped reuse whenever a Fiber.scheduler already exists before it enters Sync {}; custom middleware or background code that installs a scheduler with an unclear lifecycle can therefore keep renderer clients alive longer than intended. Keep those calls inside the request's scheduler lifecycle, or use the standard path where Sync {} creates and cleans up the per-request client.

config.renderer_http_keep_alive_timeout is deprecated and ignored: the async-http adapter manages connection lifecycle automatically (connections are reused within the scheduler and cleaned up when it ends). Explicitly setting it to a non-nil value in your configure block emits a deprecation warning; leaving it unset or setting it to nil is accepted silently. If you previously set it to 30 (the old default), remove the line from your configure block entirely.

To confirm reuse, compare before/after responseEnd timing and streamed RSC performance marks, and trace renderer sockets when you need to distinguish long-lived scheduler reuse from standard Puma's per-request scheduler cleanup.

Need Help?