Pro Feature — Available with React on Rails Pro. Free or very low cost for startups and small companies. Upgrade or licensing details →
- You must use React on Rails v11.0.7 or higher.
See Installation.
The Node Renderer reuses V8 VM contexts across requests for performance. This means module-level state in your server bundle persists across all SSR requests. Any unbounded caches, _.memoize calls, or growing data structures at module scope will leak memory until the worker restarts.
Essential for production:
- Set
NODE_OPTIONS=--max-old-space-size=<MB>to prevent V8 from deferring garbage collection - Enable worker rolling restarts via
allWorkersRestartIntervalanddelayBetweenIndividualWorkerRestarts - Audit your server bundle for module-level mutable state
See the Memory Leaks guide for common leak patterns and how to fix them.
node-renderer is a standalone Node application to serve React SSR requests from a Rails client. You don't need any Ruby code to setup and launch it. You can configure with the command line or with a launch file.
Generator shortcut: Running
rails generate react_on_rails:install --pro(orrails generate react_on_rails:profor existing apps) automatically createsrenderer/node-renderer.js, adds the Node Renderer process toProcfile.dev, and installs the required npm packages. See Installation for details. The manual setup below is for apps that need custom configuration.
- ENV values for the default config are (See JS Configuration for more details):
RENDERER_PORTRENDERER_HOSTRENDERER_LOG_LEVELRENDERER_BUNDLE_PATHRENDERER_WORKERS_COUNTRENDERER_PASSWORDRENDERER_ALL_WORKERS_RESTART_INTERVALRENDERER_DELAY_BETWEEN_INDIVIDUAL_WORKER_RESTARTSRENDERER_SUPPORT_MODULES
- Configure ENV values and run the command. Note, you can set port with args
-p <PORT>. For example, assuming node-renderer is in your path:RENDERER_BUNDLE_PATH=/app/.node-renderer-bundles node-renderer
- You can use a command line argument of
-p SOME_PORTto override any ENV value for the PORT.
For the most control over the setup, create a JavaScript file to start the NodeRenderer.
-
Create some project directory, let's say
renderer-app:mkdir renderer-app cd renderer-app -
Make sure you have Node.js 18+ and a JavaScript package manager such as npm, pnpm, Yarn, or bun.
-
Initialize a Node application and install the
react-on-rails-pro-node-rendererpackage.npm init -y npm install react-on-rails-pro-node-renderer # or: pnpm add react-on-rails-pro-node-renderer # or: yarn add react-on-rails-pro-node-renderer # or: bun add react-on-rails-pro-node-renderer
-
Configure a JavaScript file that will launch the rendering server per the docs in Node Renderer JavaScript Configuration. For example, create a file
renderer/node-renderer.js. Here is a simple example that uses all the defaults except for serverBundleCachePath:import path from 'path'; import reactOnRailsProNodeRenderer from 'react-on-rails-pro-node-renderer'; const config = { serverBundleCachePath: path.resolve(__dirname, '../.node-renderer-bundles'), }; reactOnRailsProNodeRenderer(config);
-
Now you can launch your renderer server with
node renderer/node-renderer.js. You will probably add a script to yourpackage.json. -
You can use a command line argument of
-p SOME_PORTto override any configured or ENV value for the port.
Create config/initializers/react_on_rails_pro.rb and configure the renderer server. See configuration values in Configuration. Pay attention to:
- Set
config.server_renderer = "NodeRenderer" - Decide whether to enable
config.prerender_caching = true. The default isfalse; turn it on only if you want Rails cache-backed SSR result caching and your cache is configured for the additional load. - Configure values beginning with
renderer_ - Use ENV values for values like
renderer_urlso that your deployed server is properly configured. If the ENV value is unset, the default for the renderer_url islocalhost:3800. - Here's a tiny example using mostly defaults:
ReactOnRailsPro.configure do |config|
config.server_renderer = "NodeRenderer"
# when this ENV value is not defined, the local server at localhost:3800 is used
config.renderer_url = ENV["REACT_RENDERER_URL"]
endThe Node Renderer executes JavaScript sent to it by the Rails application using Node.js vm.runInContext(). This makes it, by design, a remote code execution service — any client that can reach the HTTP port can execute arbitrary JavaScript on the host machine.
Node.js vm contexts are not a security boundary. Escaping a vm sandbox to access the full Node.js runtime (file system, child processes, network) is well-documented and straightforward.
To mitigate this, the renderer uses the same approach as PostgreSQL: it binds to localhost by default, so it is not reachable from the network at all. This provides two layers of defense:
- Network layer — Only processes on the same machine (or same Kubernetes pod network namespace) can reach the renderer. No remote attacker can connect.
- Authentication layer — The optional
passwordsetting (required in production-like environments) protects against unauthorized local callers.
This means a developer running the renderer locally without a password is safe by default — the renderer is only reachable from their own machine, just as a default PostgreSQL installation only accepts local connections.
When you must bind to 0.0.0.0 (e.g., separate container workloads in Docker Compose or Kubernetes separate-workload deployments):
- Always set
RENDERER_PASSWORDto a strong value - Place the renderer behind private networking or firewall rules
- Never expose the renderer port to the public internet
See JS Configuration for the host and password options, and Container Deployment for architecture-specific guidance.
Running tests that involve server-side rendering requires the Node Renderer to be running. Without it, tests will silently timeout with Net::ReadTimeout -- not crash with a clear error -- making the failure easy to misdiagnose.
A common mistake is guarding the Node Renderer configuration with Rails.env.development?, which excludes the test environment:
# config/initializers/react_on_rails_pro.rb
# WRONG -- excludes test environment
if Rails.env.development?
ReactOnRailsPro.configure do |config|
config.server_renderer = "NodeRenderer"
end
end
# CORRECT -- covers both development and test
if Rails.env.local?
ReactOnRailsPro.configure do |config|
config.server_renderer = "NodeRenderer"
end
endRails.env.local? returns true for both development and test environments (available since Rails 7.1). For older Rails versions, use Rails.env.development? || Rails.env.test?.
The Node Renderer must be started as a background process before running tests. Add a step to your CI workflow:
# .github/workflows/test.yml (GitHub Actions example)
jobs:
test:
runs-on: ubuntu-latest
env:
# Job-level: both the renderer and Rails test steps need this
RENDERER_PASSWORD: ${{ secrets.RENDERER_PASSWORD }}
steps:
- name: Start Node Renderer
run: |
node renderer/node-renderer.js &
# Wait for the renderer to be ready.
# The renderer uses cleartext HTTP/2 (h2c), so use --http2-prior-knowledge for the probe.
# --max-time 2 prevents hangs if the port is open but the process is stalled.
for i in $(seq 1 30); do
if curl -s --http2-prior-knowledge --max-time 2 http://localhost:3800/ > /dev/null 2>&1; then
echo "Node Renderer is ready"
break
fi
echo "Waiting for Node Renderer... ($i/30)"
sleep 1
done
# Fail fast if renderer never became ready
if ! curl -s --http2-prior-knowledge --max-time 2 http://localhost:3800/ > /dev/null 2>&1; then
echo "Node Renderer failed to start in time" >&2
exit 1
fiKey points:
- Readiness check: Poll port 3800 (or your configured port) before running tests. The renderer uses cleartext HTTP/2 (h2c), so the
curlprobe must include--http2-prior-knowledge. Without it,curlsends an HTTP/1.1 request that the h2c server rejects. RENDERER_PASSWORD: Must be set in the CI environment and match the value configured inreact_on_rails_pro.rb. Add it as a CI secret. Important: Declare this at the job level (not just the renderer step) so Rails can also read it when running tests.- Bundle pre-staging: You do not need to set a bundle path env var for the renderer. In CI, run
rake react_on_rails_pro:pre_stage_bundle_for_node_rendererafter the webpack build and before starting the renderer — this symlinks the compiled bundle into the renderer's cache directory, eliminating the first-request upload latency. For remote renderers, userake react_on_rails_pro:copy_assets_to_remote_vm_rendererinstead.
| Symptom | Cause | Fix |
|---|---|---|
All tests timeout with Net::ReadTimeout |
Node Renderer not running | Add the renderer start step above |
| "Connection refused" errors | Renderer started but not ready | Add the TCP readiness check loop |
| Tests pass locally but fail in CI | Rails.env.development? guard |
Change to Rails.env.local? |
| "Invalid password" errors | RENDERER_PASSWORD mismatch |
Ensure CI env var matches Rails config |
- See Memory Leaks guide.
The NodeRenderer has a protocol version on both the Rails and Node sides. If the Rails server sends a protocol version that does not match the Node side, an error is returned. Ideally, you want to keep both the Rails and Node sides at the same version.
- Installation
- Rails Options for node-renderer
- JS Options for node-renderer
- Container Deployment — Sidecar vs. separate workloads, memory tuning, autoscaling, and troubleshooting