Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4d5dcc5
feat: add REACT_ON_RAILS_BASE_PORT for concurrent worktree port manag…
justin808 Apr 15, 2026
78561e2
chore: add .gstack/ to .gitignore
justin808 Apr 18, 2026
0587a7e
fix: address review feedback on base port feature
justin808 Apr 18, 2026
47d0588
fix: second round of port selector review feedback
justin808 Apr 18, 2026
93d80d8
fix: third round of port selector review feedback
justin808 Apr 18, 2026
602ecdb
fix: fourth round of port selector review feedback
justin808 Apr 18, 2026
755dfe5
fix: fifth round of port selector review feedback
justin808 Apr 18, 2026
4d5ef21
fix: sixth round of port selector review feedback
justin808 Apr 18, 2026
12ecae2
fix: seventh round of port selector review feedback
justin808 Apr 18, 2026
592a422
fix: eighth round of port selector review feedback
justin808 Apr 18, 2026
1c3e3f1
fix: ninth round of port selector review feedback
justin808 Apr 18, 2026
57a4c31
fix: keep renderer fallbacks aligned
justin808 Apr 18, 2026
a13e792
fix: tenth round of port selector review feedback
justin808 Apr 18, 2026
99b50e3
fix: eleventh round of port selector review feedback
justin808 Apr 19, 2026
de13c1a
fix: twelfth round of port selector review feedback
justin808 Apr 22, 2026
4a7c434
Fix Pro generator expectations for renderer port env default
justin808 Apr 23, 2026
00c58af
fix: honor REACT_ON_RAILS_BASE_PORT in bin/dev prod mode
justin808 Apr 23, 2026
9617025
fix: thirteenth round of port selector review feedback
justin808 Apr 23, 2026
fd51626
fix: accept IPv6 hosts in url_port_mismatch? regex
justin808 Apr 23, 2026
d8dc1cc
fix: six round-15 review fixes around base-port consistency
justin808 Apr 23, 2026
61f0ad2
fix: round 16 review fixes — 5 design/doc polish
justin808 Apr 23, 2026
73b3fe0
fix: clean copied runtime files before bin/dev startup
justin808 Apr 25, 2026
f949a69
ci: ignore flaky GitHub PR link checks
justin808 Apr 25, 2026
0c9e2e1
fix: round 17 review polish — 4 doc/UX fixes
justin808 Apr 29, 2026
3997ea1
fix: round 18 review polish — 5 fixes from cursor + claude bots
justin808 Apr 29, 2026
4f2f0f0
fix: round 19 review polish — 5 fixes from cursor + claude bots
justin808 Apr 30, 2026
a3e4ccb
fix: round 20 review polish — 5 fixes from claude bot
justin808 Apr 30, 2026
a0af6ab
fix: round 21 review polish — case-insensitive localhost + whitespace…
justin808 May 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,4 @@ packages/**/node_modules/
junit.xml
.lycheecache
.playwright-cli/
.gstack/
8 changes: 5 additions & 3 deletions .lychee.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ exclude = [
'^https://medium\.com', # Returns 403 for automated requests
'^https://www\.linkedin\.com',
'^https://reactrails\.slack\.com',
'^https://invite\.reactrails\.com/?$', # Slack invite blocks automated requests
'^https://docs\.google\.com', # Private docs require auth
'^https://claude\.ai', # Returns 403 for automated requests

Expand Down Expand Up @@ -100,14 +101,15 @@ exclude = [
'^https://www\.diva-portal\.org/smash/get/diva2:1903931/FULLTEXT01\.pdf$', # Intermittent timeout in CI

# ============================================================================
# CHANGELOG COMPARE LINKS
# CHANGELOG GITHUB LINKS
# ============================================================================
# CHANGELOG.md contains GitHub compare links for each release. The latest
# release link always references a tag that doesn't exist yet (created after
# the changelog is committed). Exclude all compare links since they're
# auto-generated by changelog tooling.
# auto-generated by changelog tooling. GitHub PR pages also intermittently
# return 5xx responses to lychee in CI, even when the pages are live.
'^https://github\.com/shakacode/react_on_rails/compare/',
'^https://github\.com/shakacode/react_on_rails/pull/2280$', # Intermittent 502 from GitHub PR page in CI
'^https://github\.com/shakacode/react_on_rails/pull/[0-9]+$', # Intermittent 5xx from GitHub PR pages in CI
Comment thread
justin808 marked this conversation as resolved.
'^https://github\.com/shakacode/shakapacker/blob/cdf32835d3e0949952b8b4b53063807f714f9b24/package/environments/base\.js(#.*)?$', # Intermittent 502 from GitHub blob view in CI

# ============================================================================
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,23 @@ After a release, run `/update-changelog` in Claude Code to analyze commits, writ

### [Unreleased]

#### Added

- **`bin/dev` deterministic port allocation via `REACT_ON_RAILS_BASE_PORT` (and `CONDUCTOR_PORT`)**: `bin/dev` now derives Rails / webpack-dev-server / node-renderer ports from a single base port when `REACT_ON_RAILS_BASE_PORT` (or `CONDUCTOR_PORT`, for [Conductor.build](https://conductor.build) workspaces) is set: Rails = `base + 0`, webpack = `base + 1`, renderer = `base + 2`. This makes parallel worktrees and coding-agent sandboxes collision-free without per-service env vars. The priority chain is base port → explicit per-service env vars (`PORT`, `SHAKAPACKER_DEV_SERVER_PORT`) → auto-detection. **Behavior note:** when base-port mode is active, any pre-set `PORT`, `SHAKAPACKER_DEV_SERVER_PORT`, `RENDERER_PORT`, or non-matching `REACT_RENDERER_URL` is unconditionally overwritten with the derived value (a warning is printed before each override). This applies in all `bin/dev` modes including `bin/dev prod`, where `SHAKAPACKER_DEV_SERVER_PORT` is also derived/overwritten for tooling consistency even though the production-like mode does not run webpack-dev-server. [PR 3142](https://github.com/shakacode/react_on_rails/pull/3142) by [justin808](https://github.com/justin808).
- **[Pro] `bin/dev` auto-derives `REACT_RENDERER_URL` from `RENDERER_PORT`**: When only `RENDERER_PORT` is set, `bin/dev` now sets `REACT_RENDERER_URL=http://localhost:RENDERER_PORT` so Rails reaches the right port by default. Users running a remote or non-localhost node renderer (Docker service, remote host) should set `REACT_RENDERER_URL` explicitly so it is not replaced with the localhost default. [PR 3142](https://github.com/shakacode/react_on_rails/pull/3142) by [justin808](https://github.com/justin808).

#### Removed

- **[Pro]** **Removed the `--rsc-pro` install generator flag**: `--rsc` already implies Pro, so the separate mode was unnecessary. Behaviors previously gated on `--rsc-pro` (Pro verification checklist, prerelease install note, exact Pro gem pin on prereleases) now fire on `--rsc` installs. See also [Issue 3104](https://github.com/shakacode/react_on_rails/issues/3104), which tracks unrelated silent-failure bugs in the Pro upgrade automation. [PR 3105](https://github.com/shakacode/react_on_rails/pull/3105) by [ihabadham](https://github.com/ihabadham).

#### Changed

- **[Pro]** **Pro generator now creates the Node Renderer at `renderer/node-renderer.js`**: The canonical location for the Node Renderer entry point is now a dedicated top-level `renderer/` directory instead of `client/`, making it straightforward to exclude from production Docker builds that strip JS sources after bundling. Docs and Pro `spec/dummy` now use the new path consistently. Existing apps are unaffected — the generator skips files that already exist (including a legacy `client/node-renderer.js`). Fixes [Issue 3073](https://github.com/shakacode/react_on_rails/issues/3073). [PR 3165](https://github.com/shakacode/react_on_rails/pull/3165) by [justin808](https://github.com/justin808).
- **[Pro] Documentation standardized on `REACT_RENDERER_URL` env var name**: The configuration example in `docs/oss/configuration/configuration-pro.md` now shows `ENV["REACT_RENDERER_URL"]` instead of the older `ENV["RENDERER_URL"]`, aligning with the rest of the docs and the generator template. Existing apps that read `ENV["RENDERER_URL"]` in their initializer continue to work — the Pro `renderer_url` config is whichever env var the user reads in their initializer; no gem code reads either name directly. Rename the env var in your infrastructure configs (and update the initializer to match) if you want to align with the new convention. `bin/dev` now also warns when `RENDERER_URL` is set without `REACT_RENDERER_URL` so the rename doesn't silently fall back to the default renderer URL. [PR 3142](https://github.com/shakacode/react_on_rails/pull/3142) by [justin808](https://github.com/justin808).

#### Fixed

- **`bin/dev` now cleans copied runtime files before startup**: When you duplicate an app directory to run another local dev stack, `bin/dev` now removes copied stale Overmind sockets and stale `tmp/pids/server.pid` files that point to a Puma process running from another app directory. This prevents false startup failures in copied workspaces while still preserving active local sockets and pid files for the current app. [PR 3142](https://github.com/shakacode/react_on_rails/pull/3142) by [justin808](https://github.com/justin808).
- **[Pro]** **Node renderer now exposes `performance` when `supportModules: true`**: React 19's development build of `React.lazy` calls `performance.now()`, which previously threw `ReferenceError: performance is not defined` inside the node renderer's VM context unless users manually added `performance` via `additionalContext`. `performance` is now included in the default globals alongside `Buffer`, `process`, etc. Fixes [Issue 3154](https://github.com/shakacode/react_on_rails/issues/3154). [PR 3158](https://github.com/shakacode/react_on_rails/pull/3158) by [justin808](https://github.com/justin808).
- **Client startup now recovers if initialization begins during `interactive` after `DOMContentLoaded` already fired**: React on Rails now still initializes the page when the client bundle starts in the browser timing window after `DOMContentLoaded` but before the document reaches `complete`. Fixes [Issue 3150](https://github.com/shakacode/react_on_rails/issues/3150). [PR 3151](https://github.com/shakacode/react_on_rails/pull/3151) by [ihabadham](https://github.com/ihabadham).
- **Doctor accepts TypeScript server bundle entrypoints**: `react_on_rails:doctor` now resolves common source entrypoint suffixes (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`) before warning that the server bundle is missing, preventing false positives when apps use `server-bundle.ts`. [PR 3111](https://github.com/shakacode/react_on_rails/pull/3111) by [justin808](https://github.com/justin808).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ Rails and the Node Renderer run as independent workloads with their own scaling.
# config/initializers/react_on_rails_pro.rb
ReactOnRailsPro.configure do |config|
config.server_renderer = "NodeRenderer"
config.renderer_url = ENV.fetch("RENDERER_URL", "http://node-renderer-service:3800")
config.renderer_url = ENV.fetch("REACT_RENDERER_URL", "http://node-renderer-service:3800")
Comment thread
justin808 marked this conversation as resolved.
end
```

Expand Down Expand Up @@ -194,7 +194,7 @@ services:
ports:
- '3000:3000'
environment:
RENDERER_URL: 'http://renderer:3800'
REACT_RENDERER_URL: 'http://renderer:3800'
depends_on:
renderer:
condition: service_healthy
Expand Down Expand Up @@ -474,7 +474,7 @@ spec:
ports:
- containerPort: 3000
env:
- name: RENDERER_URL
- name: REACT_RENDERER_URL
value: 'http://localhost:3800'
- name: LD_PRELOAD
value: '/usr/lib/x86_64-linux-gnu/libjemalloc.so.2'
Expand Down
111 changes: 111 additions & 0 deletions docs/oss/building-features/process-managers.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,117 @@ SHAKAPACKER_DEV_SERVER_PORT=3036

When `PORT` or `SHAKAPACKER_DEV_SERVER_PORT` are set, auto-detection is skipped entirely.

### Coding Agent / CI Integration

When using coding agent tools that run multiple workspaces concurrently
([Conductor.build](https://conductor.build), OpenAI Codex, Quad Code, etc.),
set `REACT_ON_RAILS_BASE_PORT` to derive all service ports from a single value.
This eliminates the need for per-worktree `.env` files.

> **Important:** Run each live `bin/dev` stack from a separate checkout, worktree,
> or copied app directory. Starting two stacks from the exact same app path is
> not supported because build tools such as ReScript and webpack watchers share
> lock files and runtime artifacts within that directory.

`bin/dev` assigns ports using fixed offsets from the base:

| Service | Offset | Example (base=4000) |
| ------------------- | ------ | ------------------- |
| Rails server | +0 | 4000 |
| Webpack dev server | +1 | 4001 |
| Node renderer (Pro) | +2 | 4002 |
| _(reserved)_ | +3–+9 | 4003–4009 |

When a base port is detected, `bin/dev` also sets `RENDERER_PORT` and
`REACT_RENDERER_URL` automatically so the Pro Node Renderer and Rails
initializer agree on the port without any additional configuration.

> **Note:** Base-port mode derives the node renderer URL as
> `http://localhost:<port>`. If you run the renderer in a Docker container or
> on a remote host (e.g. `REACT_RENDERER_URL=http://renderer:3800`), do not use
> base-port mode — set `REACT_RENDERER_URL` explicitly and use the
> [manual worktree setup](#manual-worktree-port-setup-pro) instead. `bin/dev`
> will warn at runtime if a pre-set `REACT_RENDERER_URL` is overridden.

**Recognized environment variables** (checked in order):

1. `REACT_ON_RAILS_BASE_PORT` — the canonical base port variable; any tool can set this.
2. `CONDUCTOR_PORT` — set automatically by [Conductor.build](https://conductor.build).

> **Note on `CONDUCTOR_PORT`:** React on Rails treats `CONDUCTOR_PORT` as the
> **base** of a consecutive port block (Rails = base + 0, webpack = base + 1,
> renderer = base + 2). This interpretation is not part of a public Conductor
> API; treat `CONDUCTOR_PORT` support as best-effort until Conductor documents
> the contract. If a future Conductor release redefines `CONDUCTOR_PORT` (for
> example, to mean the Rails port itself), override by setting
> `REACT_ON_RAILS_BASE_PORT` explicitly — it takes precedence and uses the
> same derivation rules.

**Priority chain:** base port > explicit per-service env vars (`PORT`, etc.) > auto-detect free ports.

**Example: setting the base port in a tool's configuration:**

```sh
# In your agent tool's workspace setup or .env
REACT_ON_RAILS_BASE_PORT=4000
```

#### Tool-specific setup

[Conductor.build](https://conductor.build) sets `CONDUCTOR_PORT` for you — no configuration
needed. Other tools (Claude Code CLI, [OpenAI Codex](https://github.com/openai/codex) CLI or app,
plain `git worktree`, etc.) don't inject a port variable, so pick a different base per checkout
using one of the options below.

- **Per-worktree `.env` file** (tool-agnostic; gitignored by default):

```sh
# .env at the root of each worktree
REACT_ON_RAILS_BASE_PORT=4000
```

- **Claude Code `.claude/settings.json`** (per-project, checked in or local):

```json
{
"env": {
"REACT_ON_RAILS_BASE_PORT": "4000"
}
}
```

- **Shell export** (ad hoc, one session):

```sh
REACT_ON_RAILS_BASE_PORT=4000 bin/dev
```

`bin/dev` reads the variable from the process environment regardless of how it was set, so mix
and match whichever is most convenient for each tool.

If you create a new checkout by copying an existing app directory while another
`bin/dev` is running, the copy can inherit `tmp/pids/server.pid` or Overmind
socket files from the original app. `bin/dev` now cleans those copied stale
runtime files automatically on startup. If you launch processes outside
`bin/dev`, clear those files yourself before booting the copied app.

### Manual Worktree Port Setup (Pro)

If you use the [Node Renderer](./node-renderer/basics.md) (React on Rails Pro) with manual
worktrees (no base port), you need to configure the renderer port in addition to the standard
Rails and webpack ports:

```sh
# .env in each worktree
PORT=3001
SHAKAPACKER_DEV_SERVER_PORT=3036
RENDERER_PORT=3801
REACT_RENDERER_URL=http://localhost:3801
```

The renderer port must match on both sides: `RENDERER_PORT` is read by the Node process and
`REACT_RENDERER_URL` is read by the Rails-side Pro initializer.

## See Also

- [HMR and Hot Reloading](./hmr-and-hot-reloading-with-the-webpack-dev-server.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/oss/configuration/configuration-pro.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ ReactOnRailsPro.configure do |config|
# 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["RENDERER_URL"]
config.renderer_url = ENV["REACT_RENDERER_URL"]
Comment thread
justin808 marked this conversation as resolved.
Comment thread
justin808 marked this conversation as resolved.

# 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
Expand Down
12 changes: 7 additions & 5 deletions react_on_rails/lib/generators/react_on_rails/pro_setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,8 @@ def create_node_renderer
say "ℹ️ #{legacy_node_renderer_path} detected, keeping existing renderer; " \
"to migrate, move it to #{node_renderer_path} and update any references " \
"(e.g. Procfile.dev, Procfile.prod, Docker CMD / command):", :yellow
say " node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=3800 node #{node_renderer_path}", :yellow
say " node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=${RENDERER_PORT:-3800} " \
"node #{node_renderer_path}", :yellow
warn_on_stale_legacy_procfile_entry
return true
end
Expand Down Expand Up @@ -260,7 +261,7 @@ def warn_on_stale_legacy_procfile_entry
GeneratorMessages.add_warning(<<~MSG.strip)
⚠️ Procfile.dev still launches the legacy client/node-renderer.js.
After migrating the renderer file, update that line to:
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=3800 node renderer/node-renderer.js
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=${RENDERER_PORT:-3800} node renderer/node-renderer.js
MSG
end

Expand All @@ -272,7 +273,7 @@ def add_pro_to_procfile
⚠️ Procfile.dev not found. Skipping Node Renderer process addition.

You'll need to add the Node Renderer to your process manager manually:
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=3800 node renderer/node-renderer.js
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=${RENDERER_PORT:-3800} node renderer/node-renderer.js
MSG
return
end
Expand All @@ -287,7 +288,8 @@ def add_pro_to_procfile
if procfile_content.match?(/^[ \t]*node-renderer:/)
say "⚠️ Procfile.dev has a node-renderer: entry that doesn't reference " \
"renderer/node-renderer.js. Update it manually to:", :yellow
say " node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=3800 node renderer/node-renderer.js", :yellow
say " node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=${RENDERER_PORT:-3800} " \
"node renderer/node-renderer.js", :yellow
return
end

Expand All @@ -296,7 +298,7 @@ def add_pro_to_procfile
node_renderer_line = <<~PROCFILE

# React on Rails Pro - Node Renderer for SSR
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=3800 node renderer/node-renderer.js
node-renderer: RENDERER_LOG_LEVEL=debug RENDERER_PORT=${RENDERER_PORT:-3800} node renderer/node-renderer.js
Comment thread
justin808 marked this conversation as resolved.
PROCFILE

append_to_file("Procfile.dev", node_renderer_line)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,37 @@
# Shakapacker reads SHAKAPACKER_DEV_SERVER_PORT on both the Ruby (proxy) and
# JS (webpack-dev-server) sides, so no shakapacker.yml changes are needed.
#
# === Coding Agent / CI Integration ===
# Set REACT_ON_RAILS_BASE_PORT to derive all ports from a single value.
# This works with any tool (Conductor.build, Codex, Quad Code, etc.).
# Ports are assigned as: Rails = base+0, webpack = base+1, renderer = base+2.
# When set, no manual port configuration is needed.
#
# Conductor.build sets CONDUCTOR_PORT automatically, which is recognized
# as a fallback when REACT_ON_RAILS_BASE_PORT is not set.
#
# === Manual Worktree Setup ===
# Example for a second worktree:
# PORT=3001
# SHAKAPACKER_DEV_SERVER_PORT=3036
# RENDERER_PORT=3801 # React on Rails Pro only
# REACT_RENDERER_URL=http://localhost:3801 # Must match RENDERER_PORT

# Base port for coding agent tools (derives all other ports automatically)
# REACT_ON_RAILS_BASE_PORT=

# Rails server port (default: 3000 for Procfile.dev / 3001 for Procfile.dev-prod-assets)
# PORT=3000

# Webpack dev server port (default: 3035, used by shakapacker)
# SHAKAPACKER_DEV_SERVER_PORT=3035

# Node renderer port (React on Rails Pro only, default: 3800)
# RENDERER_PORT=3800

# Node renderer URL (React on Rails Pro only, must match RENDERER_PORT).
# If you set only RENDERER_PORT, bin/dev auto-derives
# REACT_RENDERER_URL=http://localhost:RENDERER_PORT. For a remote or
# non-localhost renderer (Docker service name, remote host), set
# REACT_RENDERER_URL explicitly so it is not replaced with the localhost default.
# REACT_RENDERER_URL=http://localhost:3800
Loading