Profiling Server-Side Rendering Code
This guide helps you profile server-side JavaScript running through React on Rails Pro so you can find slow paths and bottlenecks.
Use this page when you need a CPU profile of the Pro Node Renderer or an ExecJS/V8 log. For breakpoints and renderer logs, see Node Renderer Debugging. For React 19.2 Performance Tracks, browser traces, and a profiling decision guide, see React Performance Tracks and Profiling.
The examples below use the sample app in react_on_rails_pro/spec/dummy.
Prerequisite: This guide assumes you have Overmind installed. On macOS, you can install it with brew install overmind.
Profiling the Pro Node Renderer
-
Start the sample app with Overmind.
cd react_on_rails_pro/spec/dummy
overmind start -f Procfile.dev -
In a second terminal, stop only the managed
node-rendererprocess.cd react_on_rails_pro/spec/dummy
overmind stop node-renderer -
In that second terminal, restart the renderer manually with the Node inspector enabled.
cd react_on_rails_pro/spec/dummy
RENDERER_LOG_LEVEL=debug RENDERER_PORT=3800 node --inspect renderer/node-renderer.jsKeep this terminal open while you profile. In the repository dummy app you can also use
pnpm run node-renderer:debug, which runs the same renderer entry point with--inspect. In another app, use the same package script with your package manager, such asnpm run node-renderer:debugoryarn node-renderer:debug. -
Visit
chrome://inspectin Chrome. You should see the Node renderer process: -
Click the
inspectlink. This opens a dedicated DevTools window for the Node process. Open the Performance tab there. -
Click the record button.
-
Open the web app you want to test and refresh it multiple times. In the sample app, that means visiting http://localhost:3000.
-
If the page raises a
Timeout Error, temporarily increasessr_timeoutinconfig/initializers/react_on_rails_pro.rb. Running the renderer with--inspectslows SSR enough that a normal development timeout can be too short.config.ssr_timeout = 10 -
Stop performance recording.
-
Inspect the recorded profile.
Profile Analysis
The first request usually includes extra work because Rails uploads component bundles and the renderer executes the server-side bundle code.
Zoom into that first request and search for buildVM with Ctrl+F or Cmd+F.
Code that runs inside that VM context appears under runInContext. For example, server-rendered components that use renderToString show up below that frame.
For later requests, zoom into another request-sized block of work.
You should find a call to serverRenderReactComponent.
If you cannot find any requests coming to the renderer server, component caching may be the cause. You can try to disable React on Rails caching by adding the following line to config/initializers/react_on_rails_pro.rb:
config.prerender_caching = false
If the slow path includes client hydration, browser layout, or React Server Components timing, collect a separate browser trace using React Performance Tracks and Profiling. The Node profile explains renderer CPU time; the browser trace explains what happened after the response reached the browser.
Profiling Renderer With High Loads
To see renderer behavior under concurrent local traffic, use ApacheBench (ab) to make many HTTP requests to the same endpoint.
-
ApacheBench (ab)is installed on macOS by default. On Linux, install it with:sudo apt-get install apache2-utils -
Follow the steps in Profiling the Pro Node Renderer, but instead of opening the page in the browser, let
abdrive the traffic:ab -n 100 -c 10 http://localhost:3000/ -
The Node profile should show the renderer responding to concurrent requests.
-
Analyze each request-sized block as described in Profile Analysis.
Profiling ExecJS
React on Rails Pro supports profiling with ExecJS starting from version 4.0.0. You will need to do more work to profile ExecJS if you are using an older version.
If you are using v4.0.0 or later, enable the profiler by setting profile_server_rendering_js_code in the React on Rails Pro initializer:
config.profile_server_rendering_js_code = true
Then, run the app you are profiling and open some pages in it. You will find log files named isolate-0x*.log in the root of your app. Use the following command to analyze the log files:
rake react_on_rails_pro:process_v8_logs
The task converts the logs to profile.v8log.json files and moves them to the v8_profiles directory.
You can analyze the profile.v8log.json file with speedscope:
pnpm dlx speedscope /path/to/profile.v8log.json
# or with npm:
npx speedscope /path/to/profile.v8log.json
# or with Yarn:
yarn dlx speedscope /path/to/profile.v8log.json
Profiling ExecJS with Older Versions of React on Rails Pro
If you are using an older version of React on Rails Pro, you need to configure the ExecJS runtime manually.
If you are using node as the runtime for ExecJS, you can enable the profiler by adding the following code on top of the ReactOnRailsPro initializer.
class CustomRuntime < ExecJS::ExternalRuntime
def initialize
super(
name: 'Custom Node.js (with --prof)',
command: ['node --prof'],
runner_path: ExecJS.root + '/support/node_runner.js'
)
end
end
ExecJS.runtime = CustomRuntime.new
If you are using V8 as the runtime for ExecJS, you can enable the profiler by adding the following code on top of the ReactOnRailsPro initializer.
class CustomRuntime < ExecJS::ExternalRuntime
def initialize
super(
name: 'Custom V8 (with --prof)',
command: ['d8 --prof'],
runner_path: ExecJS.root + '/support/v8_runner.js'
)
end
end
After adding the code, run the app and open the pages you want to profile. You will find log files named isolate-0x*.log in the root of your app. Use these commands to analyze a log:
node --prof-process --preprocess -j isolate-0x*.log > profile.v8log.json
pnpm dlx speedscope /path/to/profile.v8log.json
# or with npm:
npx speedscope /path/to/profile.v8log.json
# or with Yarn:
yarn dlx speedscope /path/to/profile.v8log.json