Skip to content

Commit 9fedd96

Browse files
justin808claude
andauthored
docs(pro): document CI pattern for renderer-backed Rails tests (#3612)
## Summary Documents the CI pattern for running Rails tests that depend on a live React on Rails Pro Node Renderer, closing #3603. Apps that prerender React components or generate RSC payloads through the Node Renderer have Rails integration tests that need a running renderer. The failure mode is confusing: Rails reports a generic *server-rendering error*, but the real cause is that the renderer was unavailable (or reached on the wrong host) when Rails connected to `REACT_RENDERER_URL`. The safe GitHub Actions shape is easy to get subtly wrong. ## Changes **`docs/pro/node-renderer.md`** — new section **"Running Rails Tests Against the Node Renderer in CI"**: - Copy-pastable GitHub Actions snippet that starts the renderer, waits for its port, runs the Rails tests, and cleans up — all in **one step** (so the renderer stays alive for the whole test process). - `trap cleanup EXIT` to kill the renderer on pass/fail/timeout. - `TCPSocket` readiness polling instead of a fixed `sleep`. - Prints `/tmp/node-renderer.log` when readiness fails, so the real startup error is diagnosable. - A "Use `127.0.0.1`, not `localhost`" subsection explaining the IPv4/IPv6 resolution ambiguity (`RENDERER_HOST` defaults to `localhost`). - A note that `test` env needs no renderer password, and what to do for production-like `RAILS_ENV`. **`docs/pro/troubleshooting.md`** — "Connection refused to renderer" now notes that a generic server-rendering error in tests is often a renderer connection failure (not a webpack/bundle issue), flags the localhost-vs-127.0.0.1 gotcha, and cross-links the new CI section. ## Acceptance criteria (from #3603) - [x] Docs include a copy-pastable GitHub Actions snippet - [x] Snippet keeps the renderer alive for the Rails test process (single-step lifetime + EXIT trap) - [x] Snippet avoids `localhost` vs `127.0.0.1` ambiguity (both sides + readiness probe use `127.0.0.1`) - [x] Troubleshooting section notes "server rendering error" can be a renderer connection failure, not only webpack/bundle config ## Verification - Env var names (`RENDERER_HOST`, `RENDERER_PORT`, `RENDERER_WORKERS_COUNT`, `RENDERER_PASSWORD`) and the `localhost` default verified against `packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts`. - Snippet mirrors the pattern verified in the demo PR linked from the issue (shakacode/react_on_rails-demo-octochangelog-on-rails-pro#14). - Pre-commit `markdown-links`, `prettier`, and `trailing-newlines` hooks pass; all internal cross-link anchors resolve. Docs-only — no CHANGELOG entry per the project's changelog guidelines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Documentation-only changes with no runtime, security, or data-handling impact. > > **Overview** > Adds Pro documentation for running **Rails integration tests that need a live Node Renderer in CI**, aimed at the confusing failure where tests report a generic server-rendering error when the real issue is renderer connectivity. > > **`node-renderer.md`** introduces a copy-pastable **GitHub Actions** step that starts the renderer in the background, polls **`127.0.0.1`** with a **`TCPSocket`** readiness check (with early exit if the process dies), runs **`bin/rails test`** / **`bundle exec rspec`**, and tears down via **`trap cleanup EXIT`**, plus notes on single-step lifetime, logs on failure, and aligning **`REACT_RENDERER_URL`** / **`RENDERER_HOST`**. > > **`troubleshooting.md`** expands **Connection refused to renderer** to call out that symptom in tests, prefers **`127.0.0.1`** over **`localhost`** (IPv4/IPv6 mismatch with default **`RENDERER_HOST`**), and links to the new CI section. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit eba5b88. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added a Pro CI guide for running Rails tests with a live Node renderer: shows how to start the renderer within the same CI step, wait for readiness, capture logs, perform cleanup on exit, and surface failure diagnostics. * Expanded troubleshooting for renderer connection failures with clearer symptoms, configuration checks (prefer 127.0.0.1 over localhost), guidance to keep the renderer alive for test duration, and notes on expected renderer entrypoint and when a password is required. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 634a326 commit 9fedd96

2 files changed

Lines changed: 82 additions & 2 deletions

File tree

docs/pro/node-renderer.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,84 @@ To rotate the renderer password:
208208
1. Set the new `RENDERER_PASSWORD` env var on both the Rails app and the Node Renderer.
209209
2. Restart both processes. The new password takes effect immediately.
210210

211+
## Running Rails Tests Against the Node Renderer in CI
212+
213+
Apps that prerender React components — or generate RSC payloads — through the Node Renderer have Rails integration tests that depend on a **live renderer**. When the renderer is not running, or Rails connects to the wrong host, those tests fail with a server-rendering error whose root cause is a renderer connection failure, not a webpack or bundle problem. See [Connection refused to renderer](./troubleshooting.md#connection-refused-to-renderer) for that failure mode.
214+
215+
The reliable shape is to start the renderer, wait for its port to accept connections, run the Rails test command, and clean the renderer up — **all in a single CI step**. A background process started in one GitHub Actions step is not a dependable contract for the next step, so keep the renderer's lifetime inside the same step that runs the tests.
216+
217+
### GitHub Actions
218+
219+
```yaml
220+
- name: Run Rails tests with the Node Renderer
221+
env:
222+
RAILS_ENV: test
223+
# Rails reads this to find the renderer (config.renderer_url).
224+
REACT_RENDERER_URL: http://127.0.0.1:3800
225+
# Use the SAME host on both sides — see "Use 127.0.0.1, not localhost" below.
226+
RENDERER_HOST: 127.0.0.1
227+
RENDERER_PORT: 3800
228+
RENDERER_WORKERS_COUNT: 1
229+
run: |
230+
# Start the renderer in the background and capture its logs.
231+
node renderer/node-renderer.js > /tmp/node-renderer.log 2>&1 &
232+
renderer_pid=$!
233+
234+
# Always stop the renderer when this step exits (pass, fail, or timeout).
235+
cleanup() {
236+
kill "$renderer_pid" 2>/dev/null || true
237+
wait "$renderer_pid" 2>/dev/null || true
238+
}
239+
trap cleanup EXIT
240+
241+
# Wait up to 30s for the renderer port to accept connections, then run the tests.
242+
for _ in {1..30}; do
243+
# Fail fast if the renderer crashed before its port opened (missing bundle,
244+
# syntax error, port conflict) instead of waiting out the full timeout.
245+
if ! kill -0 "$renderer_pid" 2>/dev/null; then
246+
echo "::error::Renderer process exited before becoming ready."
247+
echo "::group::Renderer startup log"
248+
cat /tmp/node-renderer.log 2>/dev/null || true
249+
echo "::endgroup::"
250+
exit 1
251+
fi
252+
if ruby -rsocket -e 'TCPSocket.new("127.0.0.1", Integer(ENV.fetch("RENDERER_PORT", "3800"))).close' 2>/dev/null; then
253+
# Renderer is ready — run the tests and exit with their status. `|| exit $?`
254+
# preserves a failing exit code under `bash -e`; the trap still cleans up.
255+
bin/rails test || exit $? # or: bundle exec rspec
256+
exit 0
257+
fi
258+
sleep 1
259+
done
260+
261+
# Readiness never succeeded — surface the renderer log so the failure is diagnosable.
262+
if [ -f /tmp/node-renderer.log ]; then
263+
echo "::error::Renderer failed to start within 30 seconds."
264+
echo "::group::Renderer startup log"
265+
cat /tmp/node-renderer.log
266+
echo "::endgroup::"
267+
else
268+
echo "::error::Renderer failed to start and produced no log file."
269+
fi
270+
exit 1
271+
```
272+
273+
### Why each part matters
274+
275+
- **Single-step lifetime** — Starting the renderer and running the tests in one `run:` block guarantees the renderer is alive for the whole test process. A renderer backgrounded in a separate step may be torn down before the test step begins.
276+
- **`trap cleanup EXIT`** — Kills the renderer whether the tests pass, fail, or the readiness loop times out, so no orphaned process lingers on the runner.
277+
- **Readiness polling, not a fixed `sleep`** — The `TCPSocket` probe runs the tests as soon as the port accepts connections, instead of guessing a fixed wait. The test command starts only once the renderer can actually serve requests.
278+
- **Fail fast on a crashed renderer** — The `kill -0` liveness check at the top of the loop catches a renderer that died on startup (missing bundle, syntax error, port conflict) and prints its log immediately, instead of waiting out the full 30‑second timeout.
279+
- **`bin/rails test` or `bundle exec rspec`** — These are interchangeable; use whichever your suite runs under. Swap only that one command and leave the surrounding readiness loop, `trap`, and exit handling in place.
280+
- **Renderer logs on failure** — If readiness never succeeds, the step prints `/tmp/node-renderer.log` (collapsed under a `::group::` in the Actions UI) so you see the real startup error (missing bundle, port conflict, password mismatch) rather than a bare timeout.
281+
282+
### Use `127.0.0.1`, not `localhost`
283+
284+
Set `RENDERER_HOST` and the host inside `REACT_RENDERER_URL` to the **same literal** — `127.0.0.1`. `RENDERER_HOST` defaults to `localhost`, which can resolve to either IPv6 (`::1`) or IPv4 (`127.0.0.1`) depending on the runner's name-resolution order. If the renderer binds to one family while Rails dials the other, the connection is refused even though the renderer is running. Using `127.0.0.1` everywhere — including the readiness probe above — removes that ambiguity.
285+
286+
> [!NOTE]
287+
> `renderer/node-renderer.js` is the entry point created by the Pro generator (`bundle exec rails generate react_on_rails:pro`); adjust the path if your renderer entry point lives elsewhere. In the `test` environment the renderer password is optional, so this snippet omits it. If your CI runs the tests under a production-like `RAILS_ENV` (anything other than `development` or `test`), set the same `RENDERER_PASSWORD` on both the renderer and Rails — see [Renderer Password Security](#renderer-password-security).
288+
211289
## Eliminating Cold-Start Latency in Docker Deployments
212290

213291
When a new container starts, the Node Renderer has an empty bundle cache. The first SSR request triggers a costly 410→retry round-trip where Rails sends the full bundle over HTTP, adding 200ms–1s+ of latency depending on bundle size. In rolling deploys, this affects every new pod.

docs/pro/troubleshooting.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ For issues related to upgrading from GitHub Packages to public distribution, see
1414

1515
### Connection refused to renderer
1616

17-
**Symptom**: `Errno::ECONNREFUSED` or timeout errors when Rails tries to reach the node renderer.
17+
**Symptom**: `Errno::ECONNREFUSED` or timeout errors when Rails tries to reach the node renderer. In tests, this often surfaces as a generic **server-rendering error** — the actual root cause is that the renderer was unavailable when Rails connected to `REACT_RENDERER_URL`, not a webpack or bundle misconfiguration.
1818

1919
**Fixes**:
2020

21-
- Verify the renderer is running: `curl http://localhost:3800/`
21+
- Verify the renderer is running: `curl http://127.0.0.1:3800/`
2222
- Check that `config.renderer_url` in `config/initializers/react_on_rails_pro.rb` matches the renderer's actual port
23+
- Use the **same host literal** on both sides — prefer `127.0.0.1` over `localhost`. `RENDERER_HOST` defaults to `localhost`, which can resolve to IPv6 (`::1`) or IPv4 (`127.0.0.1`) depending on the machine's name-resolution order; if the renderer binds to one family and Rails dials the other, the connection is refused even though the renderer is running.
24+
- In CI, make sure the renderer stays alive for the entire Rails test process — start it, wait for its port, run the tests, and clean up in the same step. See [Running Rails Tests Against the Node Renderer in CI](./node-renderer.md#running-rails-tests-against-the-node-renderer-in-ci).
2325
- On Heroku, ensure the renderer is started via `Procfile.web` (see [Heroku deployment](../oss/building-features/node-renderer/heroku.md))
2426

2527
### Workers crashing with memory leaks

0 commit comments

Comments
 (0)