Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
15 changes: 15 additions & 0 deletions .github/workflows/rspec_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
DRIVER: selenium_chrome
CHROME_BIN: /usr/bin/google-chrome
USE_COVERALLS: true
RENDERER_PASSWORD: devPassword
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: this hardcodes a credential in source. Even as a dev-only password in a tutorial, it teaches poor practice. Use a GitHub Actions secret instead:

Suggested change
RENDERER_PASSWORD: devPassword
RENDERER_PASSWORD: ${{ secrets.RENDERER_PASSWORD }}

Add RENDERER_PASSWORD=devPassword to this repo's Actions secrets (Settings → Secrets and variables → Actions). That way the value is injected at runtime and not committed to the repo.


steps:
- name: Install Chrome
Expand Down Expand Up @@ -82,6 +83,20 @@ jobs:
- name: Build shakapacker chunks
run: NODE_ENV=development bundle exec bin/shakapacker

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observability: The renderer's stdout/stderr goes to the runner's default output mixed with the CI step log, making it hard to diagnose start-up failures. Redirect to a log file so it can be shown only on failure:

Suggested change
node react-on-rails-pro-node-renderer.js > /tmp/node-renderer.log 2>&1 &

Then add a step after the readiness check (or in an if: failure() step) to cat /tmp/node-renderer.log so failures are captured.

- name: Start Node renderer for SSR
run: |
node react-on-rails-pro-node-renderer.js &
echo "Waiting for Node renderer on port 3800..."
for i in $(seq 1 30); do
if nc -z localhost 3800 2>/dev/null; then
echo "Node renderer is ready"
exit 0
fi
sleep 1
done
echo "Node renderer failed to start within 30 seconds"
exit 1

Comment on lines 85 to +99
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two concerns with this readiness check:

  1. nc availability: nc (netcat) is part of the netcat-openbsd package and is present on ubuntu-latest, but it's not universally available and can differ between runner images. A portable alternative is a pure-bash TCP check:

    bash -c "</dev/tcp/localhost/3800" 2>/dev/null
  2. No crash detection: If the renderer process exits immediately (e.g., due to a missing npm dependency or a startup bug), this loop will spin for 30 seconds before giving up. The error message will just say the renderer didn't start — not why. Consider capturing the renderer's stderr to a log file and printing it on failure:

    node react-on-rails-pro-node-renderer.js > /tmp/renderer.log 2>&1 &
    RENDERER_PID=$!
    # ... readiness loop ...
    echo "Node renderer failed to start. Last log output:"
    tail -20 /tmp/renderer.log
    exit 1

- name: Run rspec with xvfb
uses: coactions/setup-xvfb@v1
with:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ client/app/bundles/comments/rescript/**/*.bs.js
# Using React on Rails default directory
/ssr-generated/

# Node renderer bundle cache
.node-renderer-bundles/

# Generated React on Rails packs
**/generated/**

Expand Down
70 changes: 70 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,76 @@ bundle exec rubocop
bundle exec rubocop -a
```

## Architecture: Three Bundle System
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use hyphenated compound modifier in heading.

Line 19 should read “Three-Bundle System” for correct compound adjective style.

Suggested edit
-## Architecture: Three Bundle System
+## Architecture: Three-Bundle System
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Architecture: Three Bundle System
## Architecture: Three-Bundle System
🧰 Tools
🪛 LanguageTool

[grammar] ~19-~19: Use a hyphen to join words.
Context: ...c rubocop -a ``` ## Architecture: Three Bundle System This project builds **thr...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` at line 19, Update the heading "Three Bundle System" to use a
hyphenated compound adjective by changing the text to "Three-Bundle System" in
the CLAUDE.md heading (locate the heading string "Three Bundle System" and
replace it with "Three-Bundle System").


This project builds **three separate Rspack bundles** via Shakapacker:

| Bundle | Config | Output | Runtime |
|--------|--------|--------|---------|
| **Client** | `clientWebpackConfig.js` | `public/packs/` | Browser |
| **Server (SSR)** | `serverWebpackConfig.js` | `ssr-generated/` | Node renderer VM sandbox |
| **RSC** | `rscWebpackConfig.js` | RSC output dir | Node renderer (full Node.js) |

### Key constraints

- The **server bundle runs in a VM sandbox** (`vm.createContext()`), NOT full Node.js. It lacks `require`, `MessageChannel`, `TextEncoder`, and other Node/browser globals.
- Use `resolve.fallback: false` (NOT `externals`) for Node builtins in the server bundle — the VM has no `require()`, so externalized `require('path')` calls will crash.
- The **RSC bundle** targets Node.js directly with the `react-server` condition, so Node builtins work normally there.
- A `BannerPlugin` injects a `MessageChannel` polyfill into the server bundle because `react-dom/server.browser` needs it at module load time.

### Build commands

```bash
# Build only the server bundle
SERVER_BUNDLE_ONLY=yes bin/shakapacker

# Build only the RSC bundle
RSC_BUNDLE_ONLY=true bin/shakapacker

# Watch modes (used by Procfile.dev)
SERVER_BUNDLE_ONLY=yes bin/shakapacker --watch
RSC_BUNDLE_ONLY=true bin/shakapacker --watch
```

## Node Renderer

React on Rails Pro's NodeRenderer must be running for SSR and RSC payload generation.

- **Port**: 3800 (configured in `config/initializers/react_on_rails_pro.rb`)
- **Launcher**: `node react-on-rails-pro-node-renderer.js`
- **Auth**: Requires `RENDERER_PASSWORD` env var (defaults to `local-dev-renderer-password` in dev/test, required with no fallback in production)
- **Started automatically** by `bin/dev` / `Procfile.dev`

### Testing requirement

**The Node renderer must be running before `bundle exec rspec`.** Tests will fail with `Net::ReadTimeout` if it is not running. CI starts it as a background process and waits up to 30 seconds for TCP readiness on port 3800.

### Enabled environments

The renderer is enabled when `Rails.env.local?` (development + test) or `REACT_USE_NODE_RENDERER=true`. Production requires explicit env var configuration.

## RSC Component Classification

React on Rails Pro auto-classifies components based on the `'use client'` directive:

- **With `'use client'`** → registered via `ReactOnRails.register()` (traditional SSR/client component)
- **Without `'use client'`** → registered via `registerServerComponent()` (React Server Component)

### Common gotcha: `.server.jsx` files

In this codebase, `.server.jsx` does NOT mean "React Server Component" — it means traditional **server-side rendering** (e.g., `StaticRouter`). If a `.server.jsx` file uses client APIs like `ReactOnRails.getStore()` or React hooks, it **needs** the `'use client'` directive to prevent RSC misclassification. The naming predates RSC and refers to the Rails SSR render path.

### Key files

| File | Purpose |
|------|---------|
| `config/webpack/rscWebpackConfig.js` | RSC bundle configuration |
| `config/webpack/rspackRscPlugin.js` | Detects `'use client'` directives, emits React Flight manifests |
| `client/app/packs/rsc-bundle.js` | RSC entry point |
| `client/app/packs/rsc-client-components.js` | Client component registration for RSC |
| `config/initializers/react_on_rails_pro.rb` | Node renderer + RSC configuration |
| `react-on-rails-pro-node-renderer.js` | Node renderer launcher |

## Conductor Compatibility (Version Managers)

### Problem
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby "3.4.6"

gem "react_on_rails", "16.6.0.rc.0"
gem "react_on_rails_pro", "16.6.0.rc.0"
gem "shakapacker", "10.0.0.rc.0"

# Bundle edge Rails instead: gem "rails", github: "rails/rails"
Expand Down
Loading
Loading