🏗️ Monorepo structure
This repository is a monorepo containing both the MIT-licensed core (the react_on_rails gem and the react-on-rails npm package) and the Pro packages (the react_on_rails_pro gem and the react-on-rails-pro and react-on-rails-pro-node-renderer npm packages). License boundaries are directory-based — see LICENSE.md for the authoritative list of which directories use which license.
When contributing:
- License compliance is critical — never add Pro code to MIT-licensed directories.
- Follow the existing directory layout for the package you are changing.
- internal/contributor-info/Releasing for instructions on releasing. Key workflow: update CHANGELOG.md before releasing (run
/update-changelog release), thenrake releasereads the version and auto-creates the GitHub release. - internal/contributor-info/pull-requests
- internal/contributor-info/rbs-type-signatures for information on RBS type signatures
- See other docs in internal/contributor-info
Note for users: End users of react_on_rails can continue using their preferred package manager (npm, yarn, pnpm, or bun). The generators automatically detect your package manager. The pnpm commands below are for contributors working on the react_on_rails codebase itself.
- Git hooks setup (automatic during normal setup):
Git hooks are installed automatically when you run the standard setup commands. They will run automatic linting on all changed files (staged + unstaged + untracked) - making commits fast while preventing CI failures.
-
After cloning the repo, run
bin/setupfrom the root directory to install all dependencies. -
After updating code via Git, to prepare all examples:
bundle && pnpm install && rake shakapacker_examples:gen_all && rake node_package && rakeSee Dev Initial Setup below for initial setup details, and Running tests for more details on running tests.
It's critical to configure your IDE/editor to ignore certain directories. Otherwise, your IDE might slow to a crawl!
- /coverage
- /tmp
- /gen-examples
- /packages/react-on-rails/lib
- /node_modules
- /react_on_rails/spec/dummy/app/assets/webpack
- /react_on_rails/spec/dummy/log
- /react_on_rails/spec/dummy/node_modules
- /react_on_rails/spec/dummy/client/node_modules
- /react_on_rails/spec/dummy/tmp
- /react_on_rails/spec/react_on_rails/dummy-for-generators
The react_on_rails/spec/dummy app is an example of the various setup techniques you can use with the gem.
There are also two such apps for React on Rails Pro: one using the Node renderer and one using ExecJS.
When you add a new feature, consider adding an example demonstrating it to the example apps.
You may also want to test your React on Rails changes with your own application. There are three main ways to do it: using local dependencies, Git dependencies, or tarballs.
To make your Rails app use a local version of our gems, use
gem "react_on_rails", path: "<React on Rails root>"and/or
gem "react_on_rails_pro", path: "<React on Rails root>/react_on_rails_pro"Note that you will need to run bundle install after making this change, but also that you will need to restart your Rails application if you make any changes to the gem.
For testing with external applications (your own Rails apps), you have several options:
This creates a tarball that mimics the published package:
cd <React on Rails root>
pnpm install
pnpm run build
cd packages/react-on-rails
pnpm pack # Creates react-on-rails-<version>.tgzThen in your external app:
pnpm add <path-to-react_on_rails>/packages/react-on-rails/react-on-rails-*.tgzThis is the most reliable approach as it exactly mimics what pnpm install react-on-rails would install.
If you need to iterate quickly, yalc provides a local publish/link workflow:
# Install yalc globally
pnpm add -g yalc
# In React on Rails root
cd <React on Rails root>
pnpm install
pnpm run build
# Publish to local yalc store
cd packages/react-on-rails
yalc publish
# In your external app
cd <your project root>
yalc add react-on-rails
pnpm installAfter making changes, run yalc push from the package directory to update all linked apps.
Note: The internal dummy apps (react_on_rails/spec/dummy, etc.) use pnpm workspaces with workspace:* protocol. They no longer require yalc.
The dummy apps are part of the pnpm workspace, so changes are automatically linked.
- Build the package:
pnpm run build(from root) - Add
console.log('Hello!')to clientStartup.ts - Rebuild:
pnpm run build - Start the dummy server:
cd react_on_rails/spec/dummy && foreman start - Navigate to
http://localhost:3000/- you'll see the message in the browser console
If you push your local changes to Git, you can use them as dependencies as follows:
Adjust depending on the repo you pushed to and commit/branch you want to use, see Bundler documentation:
gem 'react_on_rails',
git: 'https://github.com/shakacode/react_on_rails',
branch: 'main'
gem 'react_on_rails_pro',
git: 'https://github.com/shakacode/react_on_rails',
glob: 'react_on_rails_pro/react_on_rails_pro.gemspec',
branch: 'main'Unfortunately, not all package managers allow depending on a single subfolder of a Git repo.
The examples below are for the main branch of react-on-rails package.
See this issue.
pnpm add "github:shakacode/react_on_rails#main&path:packages/react-on-rails"See Yarn Git protocol documentation.
yarn add "git@github.com:shakacode/react_on_rails.git#workspace=react-on-rails&head=main"Explicitly doesn't want to support it.
This method works only for JS packages, not for Ruby gems.
Run pnpm pack in the package you modified, copy the generated file into your app or upload it somewhere, and run
npm install <tarball path/URL>or the equivalent command for your package manager.
After checking out the repo and ensuring you have Ruby and Node version managers set up (such as rvm and nvm, or rbenv and nodenv, etc.) with the correct versions active, run:
# First, verify your versions match the project requirements
ruby -v # Should show 4.0.x (latest), 3.3.x (minimum), or version in .ruby-version
node -v # Should show 22.x or version in .node-version
# Then run the setup script
bin/setup
# Or skip Pro setup (for contributors without Pro access)
bin/setup --skip-proThis single command installs all dependencies across the monorepo:
- Root pnpm and bundle dependencies
- Builds the node package
- Sets up
react_on_rails/spec/dummy - Sets up
react_on_rails_pro(if present) - Sets up
react_on_rails_pro/spec/dummy(if present) - Sets up
react_on_rails_pro/spec/execjs-compatible-dummy(if present)
If you prefer to set up manually or need more control:
- Install root dependencies:
bundle install
pnpm install- Build the node package:
rake node_package- Set up the dummy app:
cd react_on_rails/spec/dummy
bundle install
pnpm installYou can also run bin/console for an interactive prompt that will allow you to experiment.
When committing any Gemfile.lock, ensure it includes the Linux platform used by CI.
If you generated or refreshed a lockfile on macOS or another non-Linux platform and x86_64-linux is missing from the
PLATFORMS section, the lint workflow will fail. Run this in the directory that owns that lockfile before committing:
bundle lock --add-platform x86_64-linuxNote, the example and dummy apps will use your local packages/react-on-rails folder as the react-on-rails node package. This will also be done automatically for you via the rake examples:gen_all rake task.
Side note: It's critical to use the alias section of the Webpack config to avoid a double inclusion error. This has already been done for you in the example and dummy apps, but for reference:
resolve: {
alias: {
react: path.resolve('./node_modules/react'),
'react-dom': path.resolve('./node_modules/react-dom'),
},
},cd react_on_rails/
pnpm install
pnpm run buildOr run this, which builds the package, then the Webpack files for react_on_rails/spec/dummy, and runs tests in
react_on_rails/spec/dummy.
# Optionally change default capybara driver
export DRIVER=selenium_firefox
cd react_on_rails/
pnpm run dummy:specTo convert the development environment over to Shakapacker v6 instead of the default Shakapacker v8:
# Optionally change default capybara driver
export DRIVER=selenium_firefox
cd react_on_rails/
script/convert
pnpm run dummy:speccd react_on_rails/
pnpm run testcd react_on_rails/spec/dummy
rspeccd react_on_rails/
pnpm run checkTo format JavaScript/TypeScript files with Prettier:
pnpm start formatTo check formatting without fixing:
pnpm start format.listDifferentRun all linters (ESLint and RuboCop):
rake lintRun only RuboCop:
rake lint:rubocopFor v17, new Ruby code should use long-form hash syntax instead of shorthand; shorthand cleanup is tracked for v18 in #3501.
Run only ESLint:
pnpm run lintReact on Rails monitors bundle sizes in CI to prevent unexpected size increases. The CI compares your PR's bundle sizes against the base branch and fails if any package increases by more than 0.5KB.
Check current bundle sizes:
pnpm run sizeGet JSON output for programmatic use:
pnpm run size:jsonCompare your branch against the base branch:
bin/compare-bundle-sizesThis script automatically:
- Stashes any uncommitted changes
- Checks out and builds the base branch (default:
main) - Checks out and builds your current branch
- Compares the size and total execution time (loading + running) and shows a detailed report
Options:
bin/compare-bundle-sizes main # Compare against 'main'
bin/compare-bundle-sizes --hierarchical # Group results by packageIf your PR intentionally increases bundle size (e.g., adding a new feature), you can skip the bundle size check:
# Run from your PR branch
bin/skip-bundle-size-check
git add .bundle-size-skip-branch
git commit -m "Skip bundle size check for intentional size increase"
git pushThis sets your branch to skip the size check. The skip only applies to the specific branch name written to .bundle-size-skip-branch.
Important: Only skip the check when the size increase is justified. Document why the increase is acceptable in your PR description.
The CI measures sizes for:
- react-on-rails: Raw, gzip, and brotli compressed sizes
- react-on-rails-pro: Raw, gzip, and brotli compressed sizes
- react-on-rails-pro-node-renderer: Raw, gzip, and brotli compressed sizes
- Webpack bundled imports: Client-side bundle sizes when importing via webpack
To run the dummy app, it's CRITICAL to not just run rails s. You have to run foreman start with one of the Procfiles. If you don't do this, then webpack will not generate a new bundle, and you will be seriously confused when you change JavaScript and the app does not change. If you change the Webpack configs, then you need to restart Foreman. If you change the JS code for react-on-rails, you need to run pnpm run build in the project root.
Run rake for testing the gem and react_on_rails/spec/dummy. Otherwise, the rspec command only works for testing within the sample apps, like react_on_rails/spec/dummy.
If you run rspec at the top level, you'll see this message: require': cannot load such file -- rails_helper (LoadError)
If you run tests with COVERAGE=true, you can view the SimpleCov report at coverage/index.html.
The Turbolinks 5 gem is always bundled in the test app. Setting DISABLE_TURBOLINKS=TRUE in the environment suppresses the require_asset call in react_on_rails/spec/dummy/app/assets/javascripts/application_non_webpack.js.erb, so Turbolinks does not load at runtime — but the gem itself stays in the bundle to keep the gemset consistent with Gemfile.lock.
Run rake -T or rake -D to see testing options.
rake all_but_examples is typically best for developers, except if any generators changed.
See below for verifying changes to the generators.
When a new version of Shakapacker is released, the pinned version used across the test suite needs to be updated. The version is specified in several places:
Source files (update manually):
react_on_rails/Gemfile.development_dependencies— gem version pinreact_on_rails/spec/dummy/package.json— npm version pinreact_on_rails_pro/Gemfile.development_dependencies— gem version pin (Pro)react_on_rails_pro/spec/dummy/package.json— npm version pin (Pro)react_on_rails_pro/spec/execjs-compatible-dummy/Gemfile— gem version pin (Pro)react_on_rails_pro/spec/execjs-compatible-dummy/package.json— npm version pin (Pro)
Lock files (regenerated automatically):
After updating the source files above, regenerate lock files by running bundle install and pnpm install in the relevant directories:
react_on_rails/andreact_on_rails/spec/dummy/(OSS)react_on_rails_pro/andreact_on_rails_pro/spec/dummy/andreact_on_rails_pro/spec/execjs-compatible-dummy/(Pro)react_on_rails/Gemfile.lockand rootpnpm-lock.yaml
Example apps (handled automatically):
The CI-generated example apps (under gen-examples/) automatically resolve the shakapacker version via the gem dependency. The pin_shakapacker_npm_version helper in react_on_rails/rakelib/shakapacker_examples.rake ensures the npm version matches the gem.
The react_on_rails:install generator scaffolds a GitHub Actions workflow that uses pnpm/action-setup@v4. When the target app declares packageManager: pnpm@… in its package.json, the scaffold reads the pin from there. When it does not (e.g., existing Shakapacker apps that only have pnpm-lock.yaml), the scaffold has to supply an explicit version: to the action — otherwise the workflow fails before installing dependencies.
That fallback comes from CI_PNPM_FALLBACK_VERSION in react_on_rails/lib/generators/react_on_rails/install_generator.rb.
When to bump:
- Whenever Renovate updates the repo's own root
package.jsonpackageManagerpin to a newer pnpm version. The speckeeps the fallback pin tied to a version-specific pnpm release note(inreact_on_rails/spec/react_on_rails/generators/install_generator_spec.rb) fails whenCI_PNPM_FALLBACK_VERSIONdrifts from thepackageManagerversion inpackage.json; treat that failure as the signal to update the fallback version and release-note URL in the same PR. - Review pnpm release PRs from Renovate, at minimum on every pnpm major release and ideally on each minor as well. A new major may change the lockfile format, in which case projects scaffolded without
packageManagerwill fail with the older pinned pnpm. Renovate may open separate PRs forpackage.jsonandCI_PNPM_FALLBACK_VERSION; merge the constant-bump PR first or batch both updates into one PR to keep CI green.
What to update together (single commit):
- The
CI_PNPM_FALLBACK_VERSIONconstant value ininstall_generator.rb - The
https://github.com/pnpm/pnpm/releases/tag/v<version>URL in the comment above the constant - The root
package.jsonpackageManagerfield when bumping manually; for Renovate PRs, verify the existingpackageManagerbump matches the fallback version. The spec catches drift between these values. - Re-run
pnpm installin the repo root so the rootpnpm-lock.yamlmatches
Verification:
bundle exec rspec react_on_rails/spec/react_on_rails/generators/install_generator_spec.rb \
-e "keeps the fallback pin tied to a version-specific pnpm release note"Users who want exact reproducibility in their generated CI can commit a packageManager: pnpm@<version> field to their own package.json; the generator then omits the fallback version: entirely.
React on Rails uses a small required PR gate plus opt-in hosted validation. Contributors should validate locally before pushing, then request optimized hosted CI only when the branch is ready for remote confirmation.
- Required PR gate: Always starts for PRs and keeps the merge signal cheap.
- Optimized hosted CI: Runs path-selected hosted suites after
+ci-run-hosted,ready-for-hosted-ci, same-repository non-Dependabot release-target PRs, merge queue,main,release/*pushes, or manual dispatch. Dependabot PRs need trusted current-head+ci-run-hostedor+ci-force-fulldispatch proof before hosted jobs are enabled. - Force-full hosted CI: Runs every hosted suite only after
+ci-force-full,force-full-hosted-ci, or a manualforce_full_hosted: truedispatch. - Docs-only changes: Expensive hosted suites skip when only documentation/metadata paths change (
*.md,*.mdx,*.markdown,*.rst,*.txt,docs/,internal/,.github/ISSUE_TEMPLATE/, or.lychee.toml)
Analyzes your changes and runs appropriate tests locally before pushing. By default, it auto-detects the current PR base branch and runs optimized changed-files CI:
# Auto-detect what to test based on changed files
bin/ci-local
# Same optimized mode, made explicit
bin/ci-local --changed
# Run broad local CI where practical
bin/ci-local --all
# Quick check - only fast tests, skip slow integration tests
bin/ci-local --fastBenefits:
- Catches CI failures before pushing
- Skips irrelevant tests (e.g., Ruby tests when only JS changed)
- Provides clear summary of what passed/failed
Analyzes git changes and recommends which CI jobs to run:
# Check what changed since main
script/ci-changes-detector origin/main
# JSON output for scripting (requires jq)
CI_JSON_OUTPUT=1 script/ci-changes-detector origin/mainOutput example:
=== CI Changes Analysis ===
Changed file categories:
• Ruby source code
• JavaScript/TypeScript code
Recommended CI jobs:
✓ Lint (Ruby + JS)
✓ RSpec gem tests
✓ JS unit tests
If using Claude Code, run the /run-ci skill for interactive CI execution that:
- Analyzes your changes
- Shows recommended CI jobs
- Asks which tests to run
- Executes and reports results
✅ DO:
- Run
bin/ci-localbefore pushing to catch issues early - Use
bin/ci-local --fastduring rapid iteration - Use optimized hosted CI for final remote confirmation, and reserve force-full hosted CI for explicit broad-matrix decisions
- Separate docs-only changes into dedicated commits/PRs when possible
❌ DON'T:
- Push without running local tests first
- Mix code and docs changes if you want docs to skip CI
- Expect the required PR gate alone to catch minimum Ruby/Node version issues (use
bin/ci-local --allor force-full hosted CI for that)
The optimized detector intelligently skips unnecessary hosted work:
| Change Type | Optimized Hosted Behavior |
|---|---|
Docs only (.md, docs/) |
Skips expensive hosted suites |
| Ruby code only | Runs Ruby/lint coverage, skips JS-only suites |
| JS code only | Runs JS/lint coverage, skips Ruby-only suites |
| Workflow changes | Runs CI-infrastructure validation, no benchmarks unless explicitly labeled |
For more details, see internal/contributor-info/ci-optimization.md.
React on Rails provides PR comment commands to control CI behavior:
Post one CI command per comment. If a comment contains multiple +ci-* commands, the command workflow handles only the first one.
Maintainers and contributors with a local authenticated gh session can also
run bin/request-hosted-ci from a PR branch or add ready-for-hosted-ci directly.
That user-token label event starts optimized hosted workflows. Agents and
maintainers who want an auditable PR-visible decision should prefer the
+ci-* comment commands.
Adds the hosted CI readiness label and dispatches hosted workflows for the current head SHA:
+ci-run-hosted
What it does:
- Dispatches hosted workflows for the current head SHA
- Creates and adds the
ready-for-hosted-cilabel to the PR - Persists across future commits - subsequent pushes run optimized hosted CI
- Keeps
script/ci-changes-detectorin charge of which suites are applicable
When to use:
- The PR has passed local validation and is ready for remote confirmation
- Reviewers need hosted results before merge-readiness
- You want an auditable current-head hosted CI request
Bypasses optimized suite selection and dispatches every hosted suite:
+ci-force-full
What it does:
- Dispatches hosted workflows with
force_full_hosted: true - Creates and adds
ready-for-hosted-ciandforce-full-hosted-ci - Persists across future commits until
+ci-stop-fullremoves the force-full override
When to use:
- A maintainer explicitly wants the broad hosted matrix
- The PR changes CI detection, runtime floors, package manager behavior, release/build/publishing logic, or similarly cross-cutting infrastructure
- You need to prove optimized selection itself is not hiding a regression path
Removes hosted CI labels and returns to required-gate-only behavior:
+ci-stop-hosted
What it does:
- Removes
ready-for-hosted-ciandforce-full-hosted-cifrom the PR - Future commits use only the required gate until hosted CI is requested again
- Does not stop currently running workflows
When to use:
- The PR is back in active iteration and hosted CI should not rerun on every push
Removes the force-full override while leaving optimized hosted CI enabled if present:
+ci-stop-full
What it does:
- Removes
force-full-hosted-cifrom the PR - Leaves
ready-for-hosted-ciin place if it was present - Does not stop currently running workflows
When to use:
- The broad matrix is no longer needed, but optimized hosted CI should continue
Records that a maintainer intentionally waived hosted CI for the current PR head SHA:
+ci-skip-hosted docs-only change; markdown checks are enough
The reason is optional. If omitted, the bot records not provided.
What it does:
- Posts an audit comment with the current head SHA
- Does not cancel or block any workflow run
- Removes hosted CI labels if present
- Does not skip the required fast gate
- Does not apply to later pushes, because the waiver is bound to the SHA in the comment
Use this when you intentionally choose not to run hosted CI before merge, especially when acting as an administrator.
Use +ci-status to summarize the current head SHA, whether the PR matches the docs-only metadata paths, whether hosted CI labels are present, and whether the current SHA has a waiver comment.
Use +ci-help to list the available CI commands.
Note: ready-for-hosted-ci records that a PR ran optimized hosted CI. force-full-hosted-ci records the separate decision to bypass optimized selection.
Legacy slash commands (/run-skipped-ci, /run-skipped-tests, and /stop-run-skipped-ci) were removed. Use +ci-* commands because GitHub's comment editor uses / for built-in slash command autocomplete.
- Force-pushes:
+ci-run-hostedand+ci-force-fulldispatch workflows for the current head SHA before adding labels. If you force-push after commenting, the initial workflow run may test the old commit, but subsequent pushes follow the persistent labels. - Workflow-token labels: A label added by a workflow's
GITHUB_TOKENdoes not start newpull_requestworkflow runs by itself. That is why the comment commands dispatch workflows for the current head SHA before adding labels. - Fork PRs: Comment-command hosted CI does not dispatch same-repository workflows or add persistent labels for fork heads. A maintainer should push a trusted base-repository branch when release-target, Pro, or secret-backed CI is required.
- Waivers: The
+ci-skip-hostedcommand records the exact SHA in the PR conversation. If you push another commit, use+ci-skip-hostedagain if you still intend to waive hosted CI. - Branch operations: Avoid deleting or force-pushing branches while workflows are running, as this may cause failures.
React on Rails includes a performance benchmark workflow that measures RPS (requests per second) and latency for both Core and Pro versions.
-
Automatically on main: Benchmarks run on every push to main
-
On PRs with labels: Add the
benchmarklabel to your PR to run benchmarks -
Manual trigger: Use
gh workflow runto run benchmarks with custom parameters (see https://github.com/cli/cli#installation if you don't havegh):# Run with default parameters gh workflow run benchmark.yml # Run with custom parameters gh workflow run benchmark.yml \ -f rate=100 \ -f duration=60s \ -f connections=20 \ -f app_version=core_only
When benchmarks run, the github-action-benchmark action compares results against historical data. If performance regresses by more than 50%, the workflow will:
- Fail the CI check with
fail-on-alert: true - Post a comment on the PR explaining the regression
- Tag reviewers for attention
This helps catch performance regressions before they reach production.
Prerequisites: Install k6 and Vegeta.
You can also run the server in a separate terminal instead of backgrounding it.
Core benchmarks:
cd react_on_rails/spec/dummy
bin/prod-assets # Build production assets
bin/prod & # Start production server on port 3001
SERVER_PID=$!
cd ../..
ruby benchmarks/bench.rb
kill $SERVER_PIDPro benchmarks:
cd react_on_rails_pro/spec/dummy
bin/prod-assets
bin/prod & # Starts Rails server and node renderer
SERVER_PID=$!
cd ../..
PRO=true ruby benchmarks/bench.rb # Rails benchmarks
ruby benchmarks/bench-node-renderer.rb # Node renderer benchmarks
kill $SERVER_PIDConfiguration: Both scripts support environment variables for customization (rate, duration, connections, etc.). See the script headers in benchmarks/bench.rb and benchmarks/bench-node-renderer.rb for available options. For debugging, you may want lower DURATION and/or specific ROUTES:
DURATION=5s ROUTES=/ ruby benchmarks/bench.rbIn your Rails app add this gem with a path to your fork.
gem 'react_on_rails', path: '../relative/path/to/react_on_rails'Then run bundle.
The main installer can be run with ./bin/rails generate react_on_rails:install
Then add the npm package using one of these methods:
Option 1: pnpm pack (recommended)
# In React on Rails root
cd packages/react-on-rails
pnpm pack
# In your test app
pnpm add <path-to-react_on_rails>/packages/react-on-rails/react-on-rails-*.tgzOption 2: yalc (for rapid iteration)
# Install yalc globally if needed
pnpm add -g yalc
# In React on Rails root
cd packages/react-on-rails
yalc publish
# In your test app
yalc add react-on-railsThe generators are covered by generator tests using Rails's generator testing helpers, but it never hurts to do a sanity check and explore the API. See generator-testing.md for a script on how to run the generator on a fresh project.
rake run_rspec:shakapacker_examples_basic is a great way to run tests on one generator. Once that works, you should run rake run_rspec:shakapacker_examples. Be aware that this will create a huge number of files under a /gen-examples directory. You should be sure to exclude this directory from your IDE and delete it once your testing is done.
For comprehensive testing of generator changes, use this manual testing workflow with dedicated test applications:
1. Set up test application with clean baseline:
# Create a test Rails app
mkdir -p {project_dir}/test-app
cd {project_dir}/test-app
rails new . --skip-javascript
# Set up for testing the generator
echo 'gem "react_on_rails", path: "../react_on_rails"' >> Gemfile
yalc add react-on-rails
# Create a clean baseline tag for testing
git init && git add . && git commit -m "Initial commit"
git tag generator_testing_base
bundle install
# Clean reset to baseline state
git clean -fd && git reset --hard && git clean -fd2. Test generator commits systematically:
When testing specific generator improvements or fixes, test both Shakapacker scenarios:
Scenario A: No Shakapacker installed (fresh Rails app)
# Reset to clean baseline before each test
git clean -fd && git reset --hard generator_testing_base && git clean -fd
# Ensure no Shakapacker in Gemfile (remove if present)
# Edit Gemfile to update gem path: gem 'react_on_rails', path: '../path/to/main/repo'
bundle install
# Run generator - should install Shakapacker automatically
./bin/rails generate react_on_rails:install
# Verify Shakapacker was added to Gemfile and installed correctlyScenario B: Shakapacker already installed
# Reset to clean baseline
git clean -fd && git reset --hard generator_testing_base && git clean -fd
# Add Shakapacker to Gemfile
bundle add shakapacker --strict
# Run Shakapacker installer first
./bin/rails shakapacker:install
# Edit Gemfile to update gem path: gem 'react_on_rails', path: '../path/to/main/repo'
bundle install
# Run generator - should detect existing Shakapacker
./bin/rails generate react_on_rails:install
# Verify generator adapts to existing Shakapacker setup3. Document testing results:
For each commit tested, document:
- Generator execution success/failure for both scenarios
- Shakapacker installation/detection behavior
- Component rendering in browser
- Console output and warnings
- File generation differences between scenarios
- Specific issues found
This systematic approach ensures generator changes work correctly whether Shakapacker is pre-installed or needs to be installed by the generator.
When testing the install generator with new Rails apps, you need to use yalc for the JavaScript package to ensure React components work correctly. The Ruby gem path reference is insufficient for client-side rendering.
Problem: Using only the gem path (gem "react_on_rails", path: "../path") in a new Rails app results in React components not mounting on the client side, even though server-side rendering works fine.
Solution: Use both gem path and yalc for complete testing:
# In test app's Gemfile
gem 'react_on_rails', path: '../relative/path/to/react_on_rails'# After running the install generator AND after making any changes to the React on Rails source code
cd /path/to/react_on_rails
npm run build
npx yalc publish
# CRITICAL: Push changes to all linked apps
npx yalc push
cd /path/to/test_app
npm install
# Restart development server
bin/devyalc push after making changes to React on Rails source code. Without this step, your test app won't receive the updated package, leading to confusing behavior where changes appear to have no effect.
Alternative to Yalc: npm pack (More Reliable) For a more reliable alternative that exactly mimics real package installation:
# In react_on_rails directory
npm run build
npm pack # Creates react-on-rails-15.0.0.tgz
# In test app directory
npm install ../path/to/react_on_rails/react-on-rails-15.0.0.tgzThis approach:
- ✅ Exactly mimics real package installation
- ✅ No symlink issues across different filesystems
- ✅ More reliable for CI/CD testing
⚠️ Requires manual step after each change (can be scripted)
Why this is needed:
- The gem provides Rails integration and server-side rendering
- Yalc provides the complete JavaScript client library needed for component mounting
- Without yalc, you'll see empty divs where React components should render
Verification:
- Visit the hello_world page in browser
- Check browser console for "RENDERED HelloWorld to dom node" success message
- Confirm React component is interactive (input field updates name display)
Development Mode Console Output:
bin/dev(HMR): Shows HMR warnings and resource preload warnings (expected)bin/dev static: Shows only resource preload warnings (cleaner output)bin/dev prod: Cleanest output with minimal warnings (production-like environment)
Note: Resource preload warnings in development modes are normal and can be ignored. They occur because Shakapacker generates preload tags but scripts load asynchronously. Production mode eliminates most of these warnings.
Common Issues and Solutions:
-
React components not rendering (empty divs)
- Cause: Missing yalc setup for JavaScript package
- Solution: Follow yalc setup steps above after running generator
-
Generator fails with Shakapacker errors
- Cause: Conflicting Shakapacker versions or incomplete installation
- Solution: Clean reset and ensure consistent Shakapacker version across tests
-
Babel configuration conflicts during yalc development
- Cause: Both
babel.config.jsandpackage.json"babel" section defining presets - Solution: Remove "babel" section from
package.json, keep onlybabel.config.js
- Cause: Both
-
"Package.json not found" errors
- Cause: Generator trying to access non-existent package.json files
- Solution: Test with commits that fix this specific issue (e.g., bc69dcd0)
-
Port conflicts during testing
- Cause: Multiple development servers running
- Solution: Run
bin/dev killbefore starting new test servers
Testing Best Practices:
- Always use the double clean command:
git clean -fd && git reset --hard && git clean -fd - Test both Shakapacker scenarios for comprehensive coverage
- Document exact error messages and steps to reproduce
- Verify React component interactivity, not just rendering
- Test all development modes:
bin/dev,bin/dev static,bin/dev prod
AUTOMATED: If you've set up Lefthook (see Prerequisites), linting runs automatically on changed files before each commit.
MANUAL OPTION: If you need to run linting manually:
# Navigate to the main react_on_rails directory
cd react_on_rails/
# Run Prettier for JavaScript/TypeScript formatting
pnpm start format
# Run ESLint for JavaScript/TypeScript linting
pnpm run lint
# Run RuboCop for Ruby linting and formatting
rake lint:rubocop
# Or run all linters together
rake lintGit hooks automatically run:
- Format JavaScript/TypeScript files with Prettier (on changed files only)
- Check and fix linting issues with ESLint
- Check and fix Ruby style issues with RuboCop (on all changed files)
- Ensure trailing newlines on all files
Setup: Automatic during normal development setup
CRITICAL WORKFLOW to prevent CI failures:
# Auto-fix all linting violations after code changes
rake autofix❌ DON'T:
- Commit code that hasn't been linted locally
- Ignore formatting rules when creating new files
- Add manual formatting that conflicts with Prettier/RuboCop
✅ DO:
- Run
rake lintafter any code changes - Use
rake autofixto automatically fix all linting violations - Create new files that follow existing patterns
- Test locally before committing
When creating new template files (.jsx, .rb, etc.):
- Copy existing template structure and patterns
- Run
pnpm run eslint . --fiximmediately after creation - Verify with
rake lintbefore committing
For methods with high ABC complexity (usually formatting/display methods):
# rubocop:disable Metrics/AbcSize
def complex_formatting_method
# ... method with lots of string interpolation/formatting
end
# rubocop:enable Metrics/AbcSizeRemember: Failing CI wastes time and resources. Always lint locally first!
All linting is performed from the docker container for CI. You will need docker and docker-compose installed locally to lint code changes via the lint container. You can lint locally by running pnpm run lint
Once you have docker and docker-compose running locally, run docker-compose build lint. This will build the reactonrails_lint docker image and docker-compose lint container. The initial build is slow, but after the install, startup is very quick.
Run rake lint.
Alternately with Docker:
Run rake -D docker to see all docker linting commands for rake. rake docker:lint will run all linters. For individual rake linting commands please refer to rake -D docker for the list.
You can run specific linting for directories or files by using docker-compose run lint rubocop (file path or directory), etc.
docker-compose run lint bash sets you up to run from the container command line.
2 files require updating to update the Rubocop version:
react_on_rails.gemspecreact_on_rails/spec/dummy/Gemfile
Docker CI and Tests containers have a xvfd server automatically started for headless browser testing with selenium and Firefox.
Run docker-compose build ci to build the CI container. Run docker-compose run ci to start all rspec tests and linting. docker-compose run --entrypoint=/bin/bash will override the default CI action and place you inside the CI container in a bash session. This is what is run on Travis-CI.
Run docker-compose build tests to build the tests container. Run docker-compose run tests to start all RSpec tests.
What do project maintainers do? What sort of work is involved? sstephenson wrote in the turbolinks repo:
To keep our React on Rails demos clear, discoverable, and SEO-friendly, all demo repos follow a standardized naming and documentation structure.
Use the format:
react_on_rails-demo-v[REACT_ON_RAILS_VERSION]-[key-topics]
Examples:
react_on_rails-demo-v15-ssr-auto-registration-bundle-splittingreact_on_rails-demo-v15-react-server-componentsreact_on_rails-demo-v15-typescript-setupreact_on_rails-demo-v15-cypress-setup
Why this format?
- Clear versioning and purpose
- Easy to discover in GitHub, search engines, and documentation
- Consistent prefix for grouping demos together
# React on Rails Demo: [Topics] with v[VERSION] and Rails [VERSION]
Example:
# React on Rails Demo: SSR, Auto-Registration & Bundle Splitting with v15 and Rails 8
A fully working demo of React on Rails v[VERSION] on Rails [VERSION], showcasing [topics].
✅ Includes:
- [Topic 1]
- [Topic 2]
- [Topic 3]
📂 Repo name: `react_on_rails-demo-v[VERSION]-[topics]`
📚 Part of the [React on Rails Demo Series](https://github.com/shakacode?tab=repositories&q=react_on_rails-demo)
Example:
A fully working demo of React on Rails v15 on Rails 8, showcasing server-side rendering, file-system-based auto-registration, and intelligent bundle splitting.
✅ Includes:
- Server-Side Rendering (SSR)
- Auto-discovered components based on file structure
- Lightweight vs. heavy component splitting
- Fix for “package.json not found” install bug
📂 Repo name: `react_on_rails-demo-v15-ssr-auto-registration-bundle-splitting`
📚 Part of the [React on Rails Demo Series](https://github.com/shakacode?tab=repositories&q=react_on_rails-demo)